[LON-CAPA-cvs] cvs: modules /damieng/clean_xml clean_xml.pl html_to_xml.pm loncapa.xsd post_xml.pm pre_xml.pm validate_xml.pl xml.xsd /damieng/graphical_editor/daxe pubspec.lock pubspec.yaml /damieng/graphical_editor/daxe/.settings org.eclipse.core.resources.prefs /damieng/graphical_editor/daxe/lib LocalStrings_en.properties LocalStrings_fr.properties daxe.css daxe.dart /damieng/graphical_editor/daxe/lib/fonts STIXSubset-Bold.eot STIXSubset-Bold.ttf STIXSubset-Italic.eot STIXSubset-Italic.ttf STIXSubset-Regular.eot STIXSubset-Regular.ttf /damieng/graphical_editor/daxe/lib/images attributes.png bullet1.png bullet2.png close_dialog.png collapsed_tree.png expanded_tree.png mergebottom.png mergeright.png splitx.png splity.png /damieng/graphical_editor/daxe/lib/images/toolbar add_link.png align_center.png align_justify.png align_left.png align_right.png anchor.png document_save.png equation.png find.png history_redo.png history_undo.png insert_image.png insert_symbol.png insert_t!able.png list_lower_level.png list_rise_level.png ol.png remove_link.png remove_styles.png spellcheck.png style_bold.png style_color.png style_italic.png style_strikethrough.png style_subscript.png style_superscript.png style_underline.png ul.png /damieng/graphical_editor/daxe/lib/src attribute_dialog.dart config.dart css_map.dart cursor.dart daxe_attr.dart daxe_document.dart daxe_exception.dart daxe_node.dart file_open_dialog.dart find_dialog.dart help_dialog.dart insert_panel.dart interface_schema.dart left_offset_position.dart left_panel.dart locale.dart menu.dart menu_item.dart menubar.dart node_factory.dart node_offset_position.dart position.dart right_offset_position.dart simple_schema.dart source_window.dart strings.dart tag.dart toolbar.dart toolbar_box.dart toolbar_button.dart toolbar_item.dart toolbar_menu.dart toolbar_style_info.dart tree_item.dart tree_panel.dart undoable_edit.dart unknown_element_dialog.dart validation_dialog.dart web_page.dart /damieng/graph!ical_editor/daxe/lib/src/equations equation_dialog.dart equations.dart math_base.dart string_math_builder.dart text_metrics.dart /damieng/graphical_editor/daxe/lib/src/equations/elements math_element.dart math_frac.dart math_identifier.dart math_number.dart math_operator.dart math_over.dart math_phantom.dart math_root.dart

damieng damieng at source.lon-capa.org
Fri Apr 17 11:35:10 EDT 2015


damieng		Fri Apr 17 15:35:10 2015 EDT

  Added files:                 
    /modules/damieng/clean_xml	clean_xml.pl html_to_xml.pm loncapa.xsd 
                              	post_xml.pm pre_xml.pm validate_xml.pl 
                              	xml.xsd 
    /modules/damieng/graphical_editor/daxe	pubspec.lock pubspec.yaml 
    /modules/damieng/graphical_editor/daxe/.settings	
                                                    	org.eclipse.core.resources.prefs 
    /modules/damieng/graphical_editor/daxe/lib	
                                              	LocalStrings_en.properties 
                                              	LocalStrings_fr.properties 
                                              	daxe.css daxe.dart 
    /modules/damieng/graphical_editor/daxe/lib/fonts	
                                                    	STIXSubset-Bold.eot 
                                                    	STIXSubset-Bold.ttf 
                                                    	STIXSubset-Italic.eot 
                                                    	STIXSubset-Italic.ttf 
                                                    	STIXSubset-Regular.eot 
                                                    	STIXSubset-Regular.ttf 
    /modules/damieng/graphical_editor/daxe/lib/images	attributes.png 
                                                     	bullet1.png 
                                                     	bullet2.png 
                                                     	close_dialog.png 
                                                     	collapsed_tree.png 
                                                     	expanded_tree.png 
                                                     	mergebottom.png 
                                                     	mergeright.png 
                                                     	splitx.png 
                                                     	splity.png 
    /modules/damieng/graphical_editor/daxe/lib/images/toolbar	
                                                             	add_link.png 
                                                             	align_center.png 
                                                             	align_justify.png 
                                                             	align_left.png 
                                                             	align_right.png 
                                                             	anchor.png 
                                                             	document_save.png 
                                                             	equation.png 
                                                             	find.png 
                                                             	history_redo.png 
                                                             	history_undo.png 
                                                             	insert_image.png 
                                                             	insert_symbol.png 
                                                             	insert_table.png 
                                                             	list_lower_level.png 
                                                             	list_rise_level.png 
                                                             	ol.png 
                                                             	remove_link.png 
                                                             	remove_styles.png 
                                                             	spellcheck.png 
                                                             	style_bold.png 
                                                             	style_color.png 
                                                             	style_italic.png 
                                                             	style_strikethrough.png 
                                                             	style_subscript.png 
                                                             	style_superscript.png 
                                                             	style_underline.png 
                                                             	ul.png 
    /modules/damieng/graphical_editor/daxe/lib/src	
                                                  	attribute_dialog.dart 
                                                  	config.dart 
                                                  	css_map.dart 
                                                  	cursor.dart 
                                                  	daxe_attr.dart 
                                                  	daxe_document.dart 
                                                  	daxe_exception.dart 
                                                  	daxe_node.dart 
                                                  	file_open_dialog.dart 
                                                  	find_dialog.dart 
                                                  	help_dialog.dart 
                                                  	insert_panel.dart 
                                                  	interface_schema.dart 
                                                  	left_offset_position.dart 
                                                  	left_panel.dart 
                                                  	locale.dart 
                                                  	menu.dart 
                                                  	menu_item.dart 
                                                  	menubar.dart 
                                                  	node_factory.dart 
                                                  	node_offset_position.dart 
                                                  	position.dart 
                                                  	right_offset_position.dart 
                                                  	simple_schema.dart 
                                                  	source_window.dart 
                                                  	strings.dart 
                                                  	tag.dart 
                                                  	toolbar.dart 
                                                  	toolbar_box.dart 
                                                  	toolbar_button.dart 
                                                  	toolbar_item.dart 
                                                  	toolbar_menu.dart 
                                                  	toolbar_style_info.dart 
                                                  	tree_item.dart 
                                                  	tree_panel.dart 
                                                  	undoable_edit.dart 
                                                  	unknown_element_dialog.dart 
                                                  	validation_dialog.dart 
                                                  	web_page.dart 
    /modules/damieng/graphical_editor/daxe/lib/src/equations	
                                                            	equation_dialog.dart 
                                                            	equations.dart 
                                                            	math_base.dart 
                                                            	string_math_builder.dart 
                                                            	text_metrics.dart 
    /modules/damieng/graphical_editor/daxe/lib/src/equations/elements	
                                                                     	math_element.dart 
                                                                     	math_frac.dart 
                                                                     	math_identifier.dart 
                                                                     	math_number.dart 
                                                                     	math_operator.dart 
                                                                     	math_over.dart 
                                                                     	math_phantom.dart 
                                                                     	math_root.dart 
                                                                     	math_root_element.dart 
                                                                     	math_row.dart 
                                                                     	math_sqrt.dart 
                                                                     	math_sub.dart 
                                                                     	math_sub_sup.dart 
                                                                     	math_sup.dart 
                                                                     	math_table.dart 
                                                                     	math_table_data.dart 
                                                                     	math_table_row.dart 
                                                                     	math_text.dart 
                                                                     	math_under.dart 
                                                                     	math_under_over.dart 
    /modules/damieng/graphical_editor/daxe/lib/src/nodes	dn_anchor.dart 
                                                        	dn_area.dart 
                                                        	dn_cdata.dart 
                                                        	dn_comment.dart 
                                                        	dn_division.dart 
                                                        	dn_document.dart 
                                                        	dn_empty.dart 
                                                        	dn_equa_tex_mem.dart 
                                                        	dn_equation_mem.dart 
                                                        	dn_file.dart 
                                                        	dn_form.dart 
                                                        	dn_form_field.dart 
                                                        	dn_hidden_div.dart 
                                                        	dn_hidden_p.dart 
                                                        	dn_hr.dart 
                                                        	dn_item.dart 
                                                        	dn_line_break.dart 
                                                        	dn_list.dart 
                                                        	dn_processing_instruction.dart 
                                                        	dn_simple_type.dart 
                                                        	dn_special.dart 
                                                        	dn_string.dart 
                                                        	dn_style.dart 
                                                        	dn_style_span.dart 
                                                        	dn_table.dart 
                                                        	dn_text.dart 
                                                        	dn_witem.dart 
                                                        	dn_wlist.dart 
                                                        	nodes.dart 
                                                        	parent_updating_dn_text.dart 
                                                        	simple_type_control.dart 
    /modules/damieng/graphical_editor/daxe/lib/src/wxs	daxe_wxs.dart 
                                                      	parent.dart 
                                                      	with_sub_elements.dart 
                                                      	wxs.dart 
                                                      	wxs_all.dart 
                                                      	wxs_annotated.dart 
                                                      	wxs_annotation.dart 
                                                      	wxs_any.dart 
                                                      	wxs_attribute.dart 
                                                      	wxs_attribute_group.dart 
                                                      	wxs_choice.dart 
                                                      	wxs_complex_content.dart 
                                                      	wxs_complex_type.dart 
                                                      	wxs_documentation.dart 
                                                      	wxs_element.dart 
                                                      	wxs_exception.dart 
                                                      	wxs_explicit_group.dart 
                                                      	wxs_extension.dart 
                                                      	wxs_facet.dart 
                                                      	wxs_field.dart 
                                                      	wxs_group.dart 
                                                      	wxs_import.dart 
                                                      	wxs_include.dart 
                                                      	wxs_key.dart 
                                                      	wxs_keybase.dart 
                                                      	wxs_keyref.dart 
                                                      	wxs_list.dart 
                                                      	wxs_redefine.dart 
                                                      	wxs_restriction.dart 
                                                      	wxs_schema.dart 
                                                      	wxs_selector.dart 
                                                      	wxs_sequence.dart 
                                                      	wxs_simple_content.dart 
                                                      	wxs_simple_type.dart 
                                                      	wxs_thing.dart 
                                                      	wxs_type.dart 
                                                      	wxs_union.dart 
                                                      	wxs_unique.dart 
    /modules/damieng/graphical_editor/daxe/lib/src/xmldom	attr.dart 
                                                         	attr_impl.dart 
                                                         	cdata_section.dart 
                                                         	cdata_section_impl.dart 
                                                         	comment.dart 
                                                         	comment_impl.dart 
                                                         	document.dart 
                                                         	document_fragment.dart 
                                                         	document_fragment_impl.dart 
                                                         	document_impl.dart 
                                                         	document_type.dart 
                                                         	document_type_impl.dart 
                                                         	dom_exception.dart 
                                                         	dom_implementation.dart 
                                                         	dom_implementation_impl.dart 
                                                         	dom_parser.dart 
                                                         	element.dart 
                                                         	element_impl.dart 
                                                         	entity_reference.dart 
                                                         	entity_reference_impl.dart 
                                                         	node.dart 
                                                         	node_impl.dart 
                                                         	processing_instruction.dart 
                                                         	processing_instruction_impl.dart 
                                                         	text.dart 
                                                         	text_impl.dart 
                                                         	xmldom.dart 
    /modules/damieng/graphical_editor/daxe/lib/src/xmldom/parser	
                                                                	engine.dart 
                                                                	match_result.dart 
                                                                	state_change.dart 
                                                                	state_condition.dart 
                                                                	token.dart 
                                                                	token_char.dart 
                                                                	token_choice.dart 
                                                                	token_id.dart 
                                                                	token_item.dart 
                                                                	token_repeat.dart 
                                                                	token_rule.dart 
                                                                	token_sequence.dart 
                                                                	xml_parser.dart 
    /modules/damieng/graphical_editor/daxe/web	daxe.dart daxe.html 
    /modules/damieng/graphical_editor/daxe/web/config	XHTML_config.xml 
                                                     	XPAGES.xsd 
                                                     	XPAGES_config.xml 
                                                     	xhtml1-strict.xsd 
                                                     	xml.xsd 
    /modules/damieng/graphical_editor/loncapa_daxe	build.sh daxe.html 
                                                  	pubspec.lock 
                                                  	pubspec.yaml 
    /modules/damieng/graphical_editor/loncapa_daxe/.settings	
                                                            	org.eclipse.core.resources.prefs 
    /modules/damieng/graphical_editor/loncapa_daxe/web	
                                                      	LC_math_editor.min.js 
                                                      	LocalStrings_en.properties 
                                                      	LocalStrings_fr.properties 
                                                      	lcd_button.dart 
                                                      	lcd_strings.dart 
                                                      	loncapa_daxe.css 
                                                      	loncapa_daxe.dart 
                                                      	loncapa_daxe.html 
                                                      	parameters.xml 
                                                      	templates.xml 
    /modules/damieng/graphical_editor/loncapa_daxe/web/config	
                                                             	XHTML_config.xml 
                                                             	loncapa.xsd 
                                                             	loncapa_config.xml 
                                                             	xhtml1-strict.xsd 
                                                             	xml.xsd 
    /modules/damieng/graphical_editor/loncapa_daxe/web/images	
                                                             	block_collapsed.png 
                                                             	block_editable.png 
                                                             	block_normal.png 
                                                             	delete.png 
                                                             	grip.png 
                                                             	tex.png 
    /modules/damieng/graphical_editor/loncapa_daxe/web/nodes	
                                                            	hintgroup.dart 
                                                            	lcd_block.dart 
                                                            	lcd_parameter.dart 
                                                            	lm.dart 
                                                            	option_foil.dart 
                                                            	option_foilgroup.dart 
                                                            	option_response.dart 
                                                            	radio_foil.dart 
                                                            	radio_foilgroup.dart 
                                                            	radio_response.dart 
                                                            	rank_foil.dart 
                                                            	rank_foilgroup.dart 
                                                            	rank_response.dart 
                                                            	script_block.dart 
                                                            	simple_ui_text.dart 
                                                            	tex_mathjax.dart 
    /modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems	
                                                                         	ClickImageExample.problem.xml 
                                                                         	CustomResponse.problem.xml 
                                                                         	DropBox.problem.xml 
                                                                         	Essay.problem.xml 
                                                                         	HintFormula.problem.xml 
                                                                         	HintMathResponse.problem.xml 
                                                                         	MultipleAnswerEither.problem.xml 
                                                                         	MultipleAnswerUnordered.problem.xml 
                                                                         	Plot_curve.problem.xml 
                                                                         	Plot_data.problem.xml 
                                                                         	RadioResponse.problem.xml 
                                                                         	RandomLabelExample.problem.xml 
                                                                         	Rnumerical.problem.xml 
                                                                         	SelectFromOptions-4ConceptGoups.problem.xml 
                                                                         	SelectFromOptions-5ConceptGoups.problem.xml 
                                                                         	SelectFromOptions-6ConceptGoups.problem.xml 
                                                                         	SelectFromOptions-7ConceptGoups.problem.xml 
                                                                         	SelectFromOptions-8ConceptGoups.problem.xml 
                                                                         	SelectFromOptions-Simple.problem.xml 
                                                                         	SelectFromOptions-multilingual.problem.xml 
                                                                         	SimpleFormula.problem.xml 
                                                                         	SimpleFormulaCAS.problem.xml 
                                                                         	SimpleMatching.problem.xml 
                                                                         	SimpleMathResponse.problem.xml 
                                                                         	SimpleMathResponseR.problem.xml 
                                                                         	SimpleRank.problem.xml 
                                                                         	SimpleTrueFalse.problem.xml 
                                                                         	StringResponse.problem.xml 
                                                                         	answerdependent.problem.xml 
                                                                         	blank.problem.xml 
                                                                         	custom_equation.problem.xml 
                                                                         	customhints.problem.xml 
                                                                         	custompartial.problem.xml 
                                                                         	customunit.problem.xml 
                                                                         	dynamicgraph.problem.xml 
                                                                         	examupload.problem.xml 
                                                                         	extreme.problem.xml 
                                                                         	functionplotback.problem.xml 
                                                                         	functionplotone.problem.xml 
                                                                         	functionplottwo.problem.xml 
                                                                         	functionplotvector.problem.xml 
                                                                         	man1.jpg 
                                                                         	numMultiAnswerUnordered.problem.xml 
                                                                         	numPrePro.problem.xml 
                                                                         	numerical.problem.xml 
                                                                         	organic.problem.xml 
                                                                         	organic_hint.problem.xml 
                                                                         	randomvalueradio.problem.xml 
                                                                         	reaction.problem.xml 
                                                                         	reaction_hint.problem.xml 
                                                                         	sampleexternal.problem.xml 
                                                                         	stringPrePro.problem.xml 
    /modules/damieng/graphical_editor/loncapa_daxe/web/templates/responses	
                                                                          	customresponse.xml 
                                                                          	essayresponse.xml 
                                                                          	externalresponse.xml 
                                                                          	formularesponse.xml 
                                                                          	functionplotresponse.xml 
                                                                          	imageresponse.xml 
                                                                          	man1.jpg 
                                                                          	matchresponse.xml 
                                                                          	mathresponse.xml 
                                                                          	numericalresponse.xml 
                                                                          	optionresponse.xml 
                                                                          	organicresponse.xml 
                                                                          	radiobuttonresponse.xml 
                                                                          	rankresponse.xml 
                                                                          	reactionresponse.xml 
                                                                          	stringresponse.xml 
  Log:
  added clean_xml and graphical_editor
  
-------------- next part --------------

Index: modules/damieng/clean_xml/clean_xml.pl
+++ modules/damieng/clean_xml/clean_xml.pl
#!/usr/bin/perl

use strict;
use utf8;
use warnings;

use File::Basename;
use Try::Tiny;

use lib dirname(__FILE__);

use pre_xml;
use html_to_xml;
use post_xml;


binmode(STDOUT, ':encoding(UTF-8)');

if (scalar(@ARGV) != 1) {
  print STDERR "Usage: perl clean_xml.pl file|directory\n";
  exit(1);
}

# find the command-line argument encoding
use I18N::Langinfo qw(langinfo CODESET);
my $codeset = langinfo(CODESET);
use Encode qw(decode);
@ARGV = map { decode $codeset, $_ } @ARGV;

my $pathname = "$ARGV[0]";
if (-d "$pathname") {
  $pathname =~ s/\/$//;
  my $start = time();
  my ($converted, $failures) = convert_dir($pathname);
  my $end = time();
  my $elapsed = $end - $start;
  my $minutes = int($elapsed / 60);
  my $seconds = $elapsed - ($minutes*60);
  print "\n".scalar(@$converted)." files were converted in $minutes minutes $seconds seconds\n";
  if (scalar(@$failures) > 0) {
    print "\n".scalar(@$failures)." files need a manual fix:\n";
    foreach my $failure (@$failures) {
      print "  $failure\n";
    }
  }
} elsif (-f $pathname) {
  convert_file($pathname);
}

# Converts a directory recursively, selecting only non-version .problem/exam/survey/html/library files.
# Returns a list of files that were converted, and a list of files that could not be converted.
sub convert_dir {
  my ($dirpath) = @_;
  
  my @converted = ();
  my @failures = ();
  opendir (my $dh, $dirpath) or die $!;
  while (my $entry = readdir($dh)) {
    next if ($entry =~ m/^\./); # ignore entries starting with a period
    my $pathname = $dirpath.'/'.$entry;
    if (-d $pathname) {
      my ($new_converted, $new_failures) = convert_dir($pathname);
      push(@converted, @$new_converted);
      push(@failures, @$new_failures);
    } elsif (-f $pathname) {
      # check that the file ends in .problem, .exam, .survey, .html or .htm but not .number.*
      if (($pathname =~ /\.problem$/i || $pathname =~ /\.exam$/i || $pathname =~ /\.survey$/i ||
          $pathname =~ /\.html?$/i || $pathname =~ /\.library$/i) &&
          $pathname !~ /\.[0-9]+\.[a-z]+$/) {
        try {
          convert_file($pathname);
          push(@converted, $pathname);
        } catch {
          print "$_\n"; # continue processing even if a file cannot be converted
          push(@failures, $pathname);
        };
      }
    }
  }
  closedir($dh);
  return((\@converted, \@failures));
}

# Converts a file, creating a .xml file in the same directory.
sub convert_file {
  my ($pathname) = @_;

  # create a name for the new file
  my $newpath = $pathname.'.xml';

  print "converting $pathname...\n";

  my $textref;
  try {
    $textref = pre_xml::pre_xml($pathname);
  } catch {
    die "pre_xml error for $pathname: $_";
  };

  try {
    $textref = html_to_xml::html_to_xml($textref);
  } catch {
    die "html_to_xml error for $pathname: $_";
  };

  try {
    post_xml::post_xml($textref, $newpath);
  } catch {
    die "post_xml error for $pathname: $_";
  };
}

Index: modules/damieng/clean_xml/html_to_xml.pm
+++ modules/damieng/clean_xml/html_to_xml.pm
#!/usr/bin/perl


package html_to_xml;

use strict;
use utf8;
use warnings;
use HTML::Parser ();

# always closing, end tags are ignored:
my @empty = ('base','br','col','hr','img','input','keygen','link','meta','param','source','track','wbr', 'frame', 'embed','startouttext','endouttext');

#my @block_html = ('html','body','h1','h2','h3','h4','h5','h6','div','p','ul','ol','table','tbody','tr','td','th','dl','pre','noscript','blockquote','object','applet','embed','map','form','fieldset','iframe');


my $result;
my @stack;
my $close_warning;


# This takes non-well-formed UTF-8 LC+HTML and returns well-formed but non-valid XML LC+XHTML.
sub html_to_xml {
  my($textref) = @_;
  $result = '';
  @stack = ();
  $close_warning = '';
  my $p = HTML::Parser->new( api_version => 3,
                          start_h => [\&start, "tagname, attr, attrseq"],
                          end_h   => [\&end,   "tagname"],
                          text_h  => [\&text, "dtext"],
                          comment_h  => [\&comment, "tokens"],
                          declaration_h  => [\&declaration, "tokens"],
                          process_h  => [\&process, "token0"],
                        );
  # NOTE: by default, the HTML parser turns all attribute and elements names to lowercase
  $p->empty_element_tags(1);
  $result .= "<?xml version='1.0' encoding='UTF-8'?>\n";
  $p->parse($$textref);
  for (my $i=scalar(@stack)-1; $i>=0; $i--) {
    if ($close_warning ne '') {
      $close_warning .= ', ';
    }
    $close_warning .= $stack[$i];
    $result .= '</'.$stack[$i].'>';
  }
  if ($close_warning ne '') {
    print "Warning: the parser had to add closing tags to understand the document ($close_warning)\n";
  }
  return \$result;
}

sub start {
  my($tagname, $attr, $attrseq) = @_;
  
  # NOTE: we could do things more like web browsers, but I'm nore sure the result would be better with LON-CAPA files
  # (in problem files there are not so many missing tags)
  # See http://www.w3.org/TR/html5/syntax.html#an-introduction-to-error-handling-and-strange-cases-in-the-parser
  
  if ($tagname eq 'o:p') {
    return;
  }
  
  if ($tagname =~ /@.*\.[a-z]{2,3}$/) { # email <name at hostname>
    $result .= "<$tagname>";
    return;
  }
  
  #$tagname = lc($tagname); this is done by default by the parser
  $tagname = fix_tag($tagname);
  if (scalar(@stack) > 0 && $stack[scalar(@stack)-1] eq 'tr' && $tagname ne 'tr' && $tagname ne 'td' && $tagname ne 'th' &&
      !string_in_array(['part','block','comment','endouttext','problemtype','standalone','startouttext','tex','translated','web','while','randomlist','font','b','form'], $tagname)) {
    # NOTE: a 'part' or 'block' element between tr and td will not be valid, but changing tag order would make things worse
    # font and b will be removed in post_xml, so we can leave it for now, to handle things like <TR><FONT FACE="Palatino"><TD...
    # form is to avoid an empty form in some cases (it might not work anyway, but it is better to keep this bug the way it is)
    print "Warning: a <td> tag was added because a $tagname element was directly under a tr\n";
    start('td');
  }
  if ($tagname eq 'p' && scalar(@stack) > 0 && $stack[scalar(@stack)-1] eq 'p') {
    end('p');
  } elsif ($tagname eq 'li') {
    my $ind_li = last_index_of(\@stack, 'li');
    my $ind_ul = last_index_of(\@stack, 'ul');
    my $ind_ol = last_index_of(\@stack, 'ol');
    if ($ind_li != -1 && ($ind_ul == -1 || $ind_ul < $ind_li) && ($ind_ol == -1 || $ind_ol < $ind_li)) {
      end('li');
    }
  } elsif ($tagname eq 'tr') {
    my $ind_table = last_index_of(\@stack, 'table');
    my $ind_tr = last_index_of(\@stack, 'tr');
    if ($ind_tr != -1 && ($ind_table == -1 || $ind_table < $ind_tr)) {
      end('tr');
    }
  } elsif ($tagname eq 'td' || $tagname eq 'th') {
    my $ind_table = last_index_of(\@stack, 'table');
    my $ind_td = last_index_of(\@stack, 'td');
    my $ind_th = last_index_of(\@stack, 'th');
    my $ind_tr = last_index_of(\@stack, 'tr');
    if ($ind_tr == -1 || ($ind_table != -1 && $ind_table > $ind_tr)) {
      start('tr');
      $ind_tr = last_index_of(\@stack, 'tr');
    }
    if ($ind_td != -1 && $ind_tr < $ind_td) {
      end('td');
    } elsif ($ind_th != -1 && $ind_tr < $ind_th) {
      end('th');
    }
  } elsif ($tagname eq 'dd' || $tagname eq 'dt') {
    my $ind_dd = last_index_of(\@stack, 'dd');
    my $ind_dt = last_index_of(\@stack, 'dt');
    my $ind_dl = last_index_of(\@stack, 'dl');
    if ($ind_dl == -1) {
      start('dl');
      $ind_dl = last_index_of(\@stack, 'dl');
    }
    if ($ind_dd != -1 && ($ind_dl == -1 || $ind_dl < $ind_dd)) {
      end('dd');
    } elsif ($ind_dt != -1 && ($ind_dl == -1 || $ind_dl < $ind_dt)) {
      end('dt');
    }
  } elsif ($tagname eq 'option') {
    my $ind_option = last_index_of(\@stack, 'option');
    if ($ind_option != -1) {
      end('option');
    }
  } elsif ($tagname eq 'area') {
    my $ind_area = last_index_of(\@stack, 'area');
    if ($ind_area != -1) {
      end('area');
    }
  } elsif ($tagname eq 'a') {
    my $ind_a = last_index_of(\@stack, 'a');
    if ($ind_a != -1) {
      end('a');
    }
  } elsif ($tagname eq 'num') {
    my $ind_num = last_index_of(\@stack, 'num');
    if ($ind_num != -1) {
      end('num');
    }
  }

# HTML interpretation of non-closing elements and style is too complex (and error-prone, anyway).
# Since LON-CAPA elements are all supposed to be closed, this interpretation is SGML-like instead.
# Paragraphs inside paragraphs will be fixed later.

#   my @styles = ();
#   if ($tagname eq 'p') {
#     for (my $i=scalar(@stack)-1; $i>=0; $i--) {
#       if ($stack[$i] eq 'p') {
#         # save the styles
#         for (my $j=$i+1; $j<scalar(@stack); $j++) {
#           if (index_of(['b','i','em','strong','sub','sup'], $stack[$j]) != -1) {
#             push(@styles, $stack[$j]);
#           }
#         }
#         # close the p
#         end('p');
#         last;
#       } elsif (index_of(\@block_html, $stack[$i]) != -1) {
#         # stop looking
#         last;
#       }
#     }
#   }
  $result .= '<'.$tagname;
  my %seen = ();
  foreach my $att_name (@$attrseq) {
    my $att_name_modified = $att_name;
    $att_name_modified =~ s/[^\-a-zA-Z0-9_:.]//g;
    $att_name_modified =~ s/^[\-.0-9]*//;
    if ($att_name_modified ne '' && index($att_name_modified, ':') == -1) {
      if ($seen{$att_name_modified}) {
        print "Warning: Ignoring duplicate attribute: $att_name\n";
        next;
      }
      $seen{$att_name_modified}++;
      my $att_value = $attr->{$att_name};
      $att_value =~ s/^[“”]|[“”]$//g;
      $att_value =~ s/&/&/g;
      $att_value =~ s/</</g;
      $att_value =~ s/>/>/g;
      $att_value =~ s/"/"/g;
      if ($tagname eq 'embed' && $att_name_modified eq 'script') {
        # newlines are encoded to preserve Protein Explorer scripts in embed script attributes:
        $att_value =~ s/\x0A/&#xA;/g;
        $att_value =~ s/\x0D/&#xD;/g;
      }
      if ($att_name_modified eq 'xmlns' && ($att_value eq 'http://www.w3.org/1999/xhtml' ||
          $att_value eq 'http://www.w3.org/TR/REC-html40')) {
        next;
      }
      $result .= ' '.$att_name_modified.'="'.$att_value.'"';
    }
  }
  if (index_of(\@empty, $tagname) != -1) {
    $result .= '/>';
  } else {
    $result .= '>';
    push(@stack, $tagname);
    if (scalar(@stack) > 500) {
      die "This document has a crazy depth - I'm out !";
    }
  }
  # reopen the styles, if any
  #for (my $j=0; $j<scalar(@styles); $j++) {
  #  start($styles[$j], {}, ());
  #}
}

sub end {
  my($tagname) = @_;
  
  if ($tagname eq 'o:p') {
    return;
  }
  
  $tagname = fix_tag($tagname);
  if (index_of(\@empty, $tagname) != -1) {
    return;
  }
  if ($tagname eq 'td' && scalar(@stack) > 0 && $stack[scalar(@stack)-1] eq 'th') {
    # handle <th>text</td> as if it was <th>text</th>
    $tagname = 'th';
  } elsif ($tagname eq 'th' && scalar(@stack) > 0 && $stack[scalar(@stack)-1] eq 'td') {
    # handle <td>text</th> as if it was <td>text</td>
    $tagname = 'td';
  }
  my $found = 0;
  for (my $i=scalar(@stack)-1; $i>=0; $i--) {
    if ($stack[$i] eq $tagname) {
      for (my $j=scalar(@stack)-1; $j>$i; $j--) {
        if ($close_warning ne '') {
          $close_warning .= ', ';
        }
        $close_warning .= $stack[$j];
        $result .= '</'.$stack[$j].'>';
      }
      splice(@stack, $i, scalar(@stack)-$i);
      $found = 1;
      last;
    } elsif (index_of(\@stack, 'web') != -1) {
      die "There is a web element with missing end tags inside - it has to be fixed by hand";
    }
  }
  if ($found) {
    $result .= '</'.$tagname.'>';
  } elsif ($tagname eq 'p') {
    $result .= '<p/>';
  }
}

sub text {
  my($dtext) = @_;
  $dtext =~ s/&/&/g;
  $dtext =~ s/</</g;
  $dtext =~ s/>/>/g;
  $dtext =~ s/"/"/g;
  $result .= $dtext;
}

sub comment {
  my($tokens) = @_;
  # NOTE: the HTML parser thinks this is a comment: </ br>
  # and LON-CAPA has sometimes turned that into <![CDATA[</ br>]]>
  foreach my $comment (@$tokens) {
    $comment =~ s/--/- /g;
    $comment =~ s/^-|-$/ /g;
    $result .= '<!--'.$comment.'-->';
  }
}

sub declaration {
  my($tokens) = @_;
  # ignore them
  #$result .= '<!';
  #$result .= join(' ', @$tokens);
  #$result .= '>';
}

sub process {
  my($token0) = @_;
  if ($token0 ne '') {
    $result .= '<?'.$token0.'>';
  }
}

sub index_of {
  my ($array, $value) = @_;
  for (my $i=0; $i<scalar(@{$array}); $i++) {
    if ($array->[$i] eq $value) {
      return $i;
    }
  }
  return -1;
}

sub last_index_of {
  my ($array, $value) = @_;
  for (my $i=scalar(@{$array})-1; $i>=0; $i--) {
    if ($array->[$i] eq $value) {
      return $i;
    }
  }
  return -1;
}

sub fix_tag {
  my ($tag) = @_;
  #$tag = lc($tag); this is done by default by the parser
  if ($tag !~ /^[a-zA-Z_][a-zA-Z0-9_\-\.]*$/) {
    print "Warning: bad start tag:'".$tag."'";
    if ($tag =~ /<[a-zA-Z]/) {
      $tag =~ s/^[^<]*<//; # a<b -> b
    }
    if ($tag =~ /[a-zA-Z]=/) {
      $tag =~ s/=.*$//; # a=b -> a
    }
    if ($tag =~ /[a-zA-Z]\//) {
      $tag =~ s/\/.*$//; # a/b -> a
    }
    if ($tag =~ /:/) {
      # a:b -> b except when : at the end
      if ($tag =~ /^[^:]*:$/) {
        $tag =~ s/://;
      } else {
        $tag =~ s/^.*://;
      }
    }
    $tag =~ s/^[0-9\-\.]+//;
    $tag =~ s/[^a-zA-Z0-9_\-\.]//g;
    print " (converted to $tag)\n";
  }
  return($tag);
}


##
# Tests if a string is in an array (using eq) (to avoid Smartmatch warnings with $value ~~ @array)
# @param {Array<string>} array - reference to the array of strings
# @param {string} value - the string to look for
# @returns 1 if found, 0 otherwise
##
sub string_in_array {
  my ($array, $value) = @_;
  foreach my $v (@{$array}) {
    if ($v eq $value) {
      return 1;
    }
  }
  return 0;
}


1;
__END__

Index: modules/damieng/clean_xml/loncapa.xsd
+++ modules/damieng/clean_xml/loncapa.xsd
<?xml version="1.0" encoding="UTF-8"?><xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xml:lang="en">
  <xs:annotation>
    <xs:documentation>
      XML schema for LON-CAPA 2 documents.
    </xs:documentation>
  </xs:annotation>
  
  <xs:import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="xml.xsd">
    <xs:annotation>
      <xs:documentation>
        This import is needed to use xml:space="preserve".
      </xs:documentation>
    </xs:annotation>
  </xs:import>
  
  <xs:annotation>
    <xs:documentation>
      Shared simple types.
    </xs:documentation>
  </xs:annotation>
  <xs:simpleType name="perl">
    <xs:restriction base="xs:string">
      <xs:pattern value="\s*-?(($|&)([#|$]*[A-Za-z][\w_]*|\{[A-Za-z][\w_]*\}))([\[\{].+[\]\}])*(\([^$&\)]+\))*\s*"/>
    </xs:restriction>
  </xs:simpleType>
  <xs:simpleType name="int-or-perl">
    <xs:union memberTypes="xs:int perl"/>
  </xs:simpleType>
  <xs:simpleType name="non-negative-int-or-perl">
    <xs:union memberTypes="xs:nonNegativeInteger perl"/>
  </xs:simpleType>
  <xs:simpleType name="decimal-or-perl">
    <xs:union memberTypes="xs:decimal perl"/>
  </xs:simpleType>
  <xs:simpleType name="real">
    <xs:restriction base="xs:string">
      <xs:pattern value="[+-]?\d*\.?\d*([eE][+-]?\d+)?"/>
    </xs:restriction>
  </xs:simpleType>
  <xs:simpleType name="real-or-perl">
    <xs:union memberTypes="real perl"/>
  </xs:simpleType>
  <xs:simpleType name="yesno">
    <xs:restriction base="xs:string">
      <xs:enumeration value="yes"/>
      <xs:enumeration value="no"/>
    </xs:restriction>
  </xs:simpleType>
  <xs:simpleType name="yesno-or-perl">
    <xs:union memberTypes="yesno perl"/>
  </xs:simpleType>
  <xs:simpleType name="onoff">
    <xs:restriction base="xs:string">
      <xs:enumeration value="on"/>
      <xs:enumeration value="off"/>
    </xs:restriction>
  </xs:simpleType>
  <xs:simpleType name="onoff-or-perl">
    <xs:union memberTypes="onoff perl"/>
  </xs:simpleType>
  <xs:simpleType name="color">
    <xs:restriction base="xs:string">
      <xs:pattern value="[x#][\da-fA-F]{6}"/>
    </xs:restriction>
  </xs:simpleType>
  <xs:simpleType name="color-or-perl">
    <xs:union memberTypes="color perl"/>
  </xs:simpleType>
  <xs:simpleType name="location">
    <xs:restriction base="xs:string">
      <xs:enumeration value="random"/>
      <xs:enumeration value="top"/>
      <xs:enumeration value="bottom"/>
    </xs:restriction>
  </xs:simpleType>
  <xs:simpleType name="location-or-perl">
    <xs:union memberTypes="location perl"/>
  </xs:simpleType>
  
  <xs:annotation>
    <xs:documentation>
      Shared attributes
    </xs:documentation>
  </xs:annotation>
  <xs:attribute default="normalsize" name="TeXsize">
    <xs:annotation>
      <xs:documentation>
        Size of LaTeX fonts used in printing.
        
        Possible values of TeXsize attribute:
        - tiny: smallest
        - scriptsize: very small
        - footnotesize: smaller
        - small: small
        - normalsize: normal
        - large: large
        - Large: larger
        - LARGE: even larger
        - huge: still larger
        - Huge: largest
        
        Note, that all parameters coincide with standard LaTeX commands for changing font size though you do not escape them.
      </xs:documentation>
    </xs:annotation>
    <xs:simpleType>
      <xs:union memberTypes="perl">
        <xs:simpleType>
          <xs:restriction base="xs:string">
            <xs:enumeration value="tiny"/>
            <xs:enumeration value="scriptsize"/>
            <xs:enumeration value="footnotesize"/>
            <xs:enumeration value="small"/>
            <xs:enumeration value="normalsize"/>
            <xs:enumeration value="large"/>
            <xs:enumeration value="Large"/>
            <xs:enumeration value="LARGE"/>
            <xs:enumeration value="huge"/>
            <xs:enumeration value="Huge"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:union>
    </xs:simpleType>
  </xs:attribute>
  
  <xs:annotation>
    <xs:documentation>
      Problem (root element)
    </xs:documentation>
  </xs:annotation>
  <xs:element name="problem">
    <xs:annotation>
      <xs:documentation>
        Root for .problem documents.
        
        This must be first in the file. It sets up the header of the webpage and generates the submit buttons. It also handles due dates properly.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-with-parts"/>
        <xs:group ref="inserts"/>
        <xs:element ref="allow"/>
        <xs:element ref="meta"/>
        <xs:element ref="parameter"/>
        <xs:element ref="parserlib"/>
        <xs:element ref="scriptlib"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  
  <xs:annotation>
    <xs:documentation>
      Library (root element)
    </xs:documentation>
  </xs:annotation>
  <xs:element name="library">
    <xs:annotation>
      <xs:documentation>
        Root for .library documents.
        A LON-CAPA .library file can contain just a script block, or just response items, or both.
        Library content is loaded into a problem statement by using an <import> tag.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-with-responses"/>
        <xs:group ref="inserts"/>
        <xs:element ref="part"/>
        <xs:element ref="parserlib"/>
        <xs:element ref="scriptlib"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  
  <xs:annotation>
    <xs:documentation>
      Groups of block and inline elements.
    </xs:documentation>
  </xs:annotation>
  <xs:group name="text-with-parts">
    <xs:annotation>
      <xs:documentation>
        List of block and inline elements mixed with text. These elements can contain parts and responses.
      </xs:documentation>
    </xs:annotation>
    <xs:choice>
      <xs:element ref="part"/>
      <xs:group ref="responses"/>
      <xs:group ref="blocks-with-parts"/>
      <xs:group ref="inlines"/>
    </xs:choice>
  </xs:group>
  <xs:group name="text-with-responses">
    <xs:annotation>
      <xs:documentation>
        List of block and inline elements mixed with text. These elements can contain responses but not parts.
      </xs:documentation>
    </xs:annotation>
    <xs:choice>
      <xs:group ref="responses"/>
      <xs:group ref="blocks-with-responses"/>
      <xs:group ref="inlines"/>
    </xs:choice>
  </xs:group>
  <xs:group name="text-only">
    <xs:annotation>
      <xs:documentation>
        List of block and inline elements mixed with text. These elements cannot contain responses or parts.
      </xs:documentation>
    </xs:annotation>
    <xs:choice>
      <xs:group ref="blocks-with-text"/>
      <xs:group ref="inlines"/>
    </xs:choice>
  </xs:group>
  <xs:group name="universal-blocks">
    <xs:annotation>
      <xs:documentation>
        Blocks with a content that does not depend on the scope, and that can appear anywhere with text and blocks.
      </xs:documentation>
    </xs:annotation>
    <xs:choice>
      <xs:element ref="randomlabel"/>
      <xs:element ref="import"/>
      <xs:element ref="while"/>
      <xs:element ref="tex"/>
      <xs:element ref="print"/>
      <xs:element ref="web"/>
      <xs:element ref="standalone"/>
      <xs:element ref="script"/>
      <xs:element ref="languageblock"/>
      <xs:element ref="translated"/>
      <xs:element ref="window"/>
      <xs:element ref="windowlink"/>
      <xs:element ref="togglebox"/>
      <xs:element ref="instructorcomment"/>
      <xs:element ref="comment"/>
      <xs:element ref="gnuplot"/>
      <xs:element ref="organicstructure"/>
      <xs:element ref="drawimage"/>
      <xs:element ref="solved"/>
      <xs:element ref="notsolved"/>
      
      <xs:group ref="heading"/>
      <xs:element ref="noscript"/>
      <xs:element ref="header"/>
      <xs:element ref="footer"/>
      <xs:element ref="aside"/>
      <xs:element ref="pre"/>
      <xs:element ref="hr"/>
      <xs:element ref="address"/>
      <xs:element ref="blockquote"/>
      <xs:element ref="figure"/>
      <xs:element ref="object"/>
      <xs:element ref="applet"/>
      <xs:element ref="embed"/>
      <xs:element ref="video"/>
      <xs:element ref="audio"/>
      <xs:element ref="map"/>
      <xs:element ref="canvas"/>
      <xs:element ref="form"/>
      <xs:element ref="fieldset"/>
      <xs:element ref="iframe"/>
    </xs:choice>
  </xs:group>
  <xs:group name="blocks-with-parts">
    <xs:choice>
      <xs:group ref="universal-blocks"/>
      
      <xs:element name="block" type="block-with-parts"/>
      <xs:element name="problemtype" type="problemtype-with-parts"/>
      <xs:element name="randomlist" type="randomlist-with-parts"/>
      
      <xs:element name="section" type="section-with-parts"/>
      <xs:element name="div" type="div-with-parts"/>
      <xs:element name="p" type="p-with-responses"/>
      <xs:element name="ul" type="ul-with-parts"/>
      <xs:element name="ol" type="ol-with-parts"/>
      <xs:element name="dl" type="dl-with-parts"/>
      <xs:element name="table" type="table-with-parts"/>
    </xs:choice>
  </xs:group>
  <xs:group name="blocks-with-responses">
    <xs:choice>
      <xs:group ref="universal-blocks"/>
      
      <xs:element name="block" type="block-with-responses"/>
      <xs:element name="problemtype" type="problemtype-with-responses"/>
      <xs:element name="randomlist" type="randomlist-with-responses"/>
      
      <xs:element name="section" type="section-with-responses"/>
      <xs:element name="div" type="div-with-responses"/>
      <xs:element name="p" type="p-with-responses"/>
      <xs:element name="ul" type="ul-with-responses"/>
      <xs:element name="ol" type="ol-with-responses"/>
      <xs:element name="dl" type="dl-with-responses"/>
      <xs:element name="table" type="table-with-responses"/>
    </xs:choice>
  </xs:group>
  <xs:group name="blocks-with-text">
    <xs:choice>
      <xs:group ref="universal-blocks"/>
      
      <xs:element name="block" type="block-with-text"/>
      <xs:element name="problemtype" type="problemtype-with-text"/>
      <xs:element name="randomlist" type="randomlist-with-text"/>
      
      <xs:element name="section" type="section-with-text"/>
      <xs:element name="div" type="div-with-text"/>
      <xs:element name="p" type="p-with-text"/>
      <xs:element name="ul" type="ul-with-text"/>
      <xs:element name="ol" type="ol-with-text"/>
      <xs:element name="dl" type="dl-with-text"/>
      <xs:element name="table" type="table-with-text"/>
    </xs:choice>
  </xs:group>
  <xs:group name="inlines">
    <xs:choice>
      <xs:element ref="display"/>
      <xs:element ref="m"/>
      <xs:element ref="lm"/>
      <xs:element ref="chem"/>
      <xs:element ref="num"/>
      <xs:element ref="parse"/>
      <xs:element ref="algebra"/>
      <xs:element ref="textline"/>
      <xs:element ref="displayweight"/>
      <xs:element ref="displaystudentphoto"/>
      
      <xs:element ref="span"/>
      <xs:element ref="a"/>
      <xs:element ref="strong"/>
      <xs:element ref="em"/>
      <xs:element ref="b"/>
      <xs:element ref="i"/>
      <xs:element ref="sup"/>
      <xs:element ref="sub"/>
      <xs:element ref="code"/>
      <xs:element ref="kbd"/>
      <xs:element ref="samp"/>
      <xs:element ref="cite"/>
      <xs:element ref="q"/>
      <xs:element ref="tt"/>
      <xs:element ref="ins"/>
      <xs:element ref="del"/>
      <xs:element ref="var"/>
      <xs:element ref="small"/>
      <xs:element ref="big"/>
      <xs:element ref="br"/>
      <xs:element ref="img"/>
      <xs:element ref="input"/>
      <xs:element ref="select"/>
      <xs:element ref="textarea"/>
      <xs:element ref="label"/>
      <xs:element ref="button"/>
    </xs:choice>
  </xs:group>
  <xs:group name="inserts">
    <xs:annotation>
      <xs:documentation>
        List of elements that insert something if a condition is verified.
      </xs:documentation>
    </xs:annotation>
    <xs:choice>
      <xs:element ref="displaytitle"/>
      <xs:element ref="displayduedate"/>
      <xs:element ref="preduedate"/>
      <xs:element ref="postanswerdate"/>
    </xs:choice>
  </xs:group>
  
  <xs:annotation>
    <xs:documentation>
      List of responses
    </xs:documentation>
  </xs:annotation>
  <xs:group name="responses">
    <xs:choice>
      <xs:group ref="inlineResponses"/>
      <xs:group ref="blockResponses"/>
    </xs:choice>
  </xs:group>
  <xs:group name="inlineResponses">
    <xs:choice>
      <xs:element ref="stringresponse"/>
      <xs:element ref="optionresponse"/>
      <xs:element ref="numericalresponse"/>
      <xs:element ref="formularesponse"/>
      <xs:element ref="mathresponse"/>
      <xs:element ref="organicresponse"/>
      <xs:element ref="reactionresponse"/>
      <xs:element ref="customresponse"/>
      <xs:element ref="externalresponse"/>
    </xs:choice>
  </xs:group>
  <xs:group name="blockResponses">
    <xs:choice>
      <xs:element ref="essayresponse"/>
      <xs:element ref="radiobuttonresponse"/>
      <xs:element ref="matchresponse"/>
      <xs:element ref="rankresponse"/>
      <xs:element ref="imageresponse"/>
      <xs:element ref="functionplotresponse"/>
      <xs:element ref="dataresponse"/>
    </xs:choice>
  </xs:group>
  <xs:attributeGroup name="response-identification">
    <xs:attribute name="id" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          Unique identifier for the response in the document. If this isn’t set, it will be set during the publication step. It is used to assign parameter names in a way that can be tracked if an instructor modifies by hand.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="name" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          If set, the name will be used by the resource assembly tool when one is modifying parameters.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:attributeGroup>
  
  <xs:annotation>
    <xs:documentation>
      String response
    </xs:documentation>
  </xs:annotation>
  <xs:element name="stringresponse">
    <xs:annotation>
      <xs:documentation>
        Query for a string.
        An internal textline tag is necessary for the student’s response to go in. It can check the string for either case or order.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
        <xs:element ref="hiddenline"/>
        <xs:element ref="hintgroup"/>
        <xs:element ref="stringhint"/>
        <xs:element ref="hintpart"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="response-identification"/>
      <xs:attribute name="answer" type="xs:string" use="required">
        <xs:annotation>
          <xs:documentation>
            the correct answer, either a perl list or scalar
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="cs" name="type">
        <xs:annotation>
          <xs:documentation>
            Specifies how the string is checked (like the CAPA styles). Possible values are:
            – cs: case sensitive, order important.
            – ci: case insensitive, order important.
            – mc: case insensitive, order unimportant.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="cs"/>
                <xs:enumeration value="ci"/>
                <xs:enumeration value="mc"/>
                <xs:enumeration value="re"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="answerdisplay" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            String to display for answer
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="preprocess" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Pre-Processor Subroutine
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  
  <xs:annotation>
    <xs:documentation>
      Essay response
    </xs:documentation>
  </xs:annotation>
  <xs:element name="essayresponse">
    <xs:annotation>
      <xs:documentation>
        Query for a long text or a line, possibly with spell checking.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
        <xs:element ref="textfield"/>
        <xs:element ref="hiddensubmission"/>
        <xs:element ref="hiddenline"/>
        <xs:element ref="hintgroup"/>
        <xs:element ref="stringhint"/>
        <xs:element ref="hintpart"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="response-identification"/>
    </xs:complexType>
  </xs:element>
  
  <xs:annotation>
    <xs:documentation>
      Radio button response
    </xs:documentation>
  </xs:annotation>
  <xs:element name="radiobuttonresponse">
    <xs:annotation>
      <xs:documentation>
        Query for a single choice among several statements.
        The value of the foils can only be true, false, or unused.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
        <xs:element name="foilgroup" type="radiobuttonresponse--foilgroup"/>
        <xs:element ref="hintgroup"/>
        <xs:element ref="radiobuttonhint"/>
        <xs:element ref="hintpart"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="response-identification"/>
      <xs:attribute name="max" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            Max Number Of Shown Foils
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="yes" name="randomize" type="yesno-or-perl">
        <xs:annotation>
          <xs:documentation>
            Randomize Foil Order
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="vertical" name="direction">
        <xs:annotation>
          <xs:documentation>
            Display Direction
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="vertical"/>
                <xs:enumeration value="horizontal"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:complexType name="radiobuttonresponse--foilgroup">
    <xs:annotation>
      <xs:documentation>Collection of Foils</xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded">
      <xs:element name="conceptgroup" type="radiobuttonresponse--conceptgroup"/>
      <xs:element name="foil" type="radiobuttonresponse--foil"/>
      <xs:element ref="comment"/>
    </xs:choice>
  </xs:complexType>
  <xs:complexType name="radiobuttonresponse--conceptgroup">
    <xs:annotation>
      <xs:documentation>
        Collection of similar foils.
        When a problem is displayed, only one of the contained foils is selected for display.
      </xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded">
      <xs:element name="foil" type="radiobuttonresponse--foil"/>
    </xs:choice>
    <xs:attribute name="concept" type="xs:string" use="required"/>
  </xs:complexType>
  <xs:complexType mixed="true" name="radiobuttonresponse--foil">
    <xs:annotation>
      <xs:documentation>
        Statement next to the radio button.
      </xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded" minOccurs="0">
      <xs:group ref="text-only"/>
      <xs:element ref="parserlib"/>
      <xs:element ref="scriptlib"/>
    </xs:choice>
    <xs:attribute name="name" type="xs:string" use="required"/>
    <xs:attribute name="value" use="required">
      <xs:annotation>
        <xs:documentation>
          Correct Option (true, false, or unused).
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="true"/>
              <xs:enumeration value="false"/>
              <xs:enumeration value="unused"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute default="random" name="location" type="location-or-perl"/>
  </xs:complexType>
  
  <xs:annotation>
    <xs:documentation>
      Option response
    </xs:documentation>
  </xs:annotation>
  <xs:element name="optionresponse">
    <xs:annotation>
      <xs:documentation>
        Query for a choice for each given statement.
        
        Option Response problems present foils to the student with drop-down boxes. The student can select the matching choice for the foils from a list of choices. Optionally, the foils may be bundled into Concept Groups and the system will select one foil from each group to display to the student.
        
        By default, the list of options is presented in front of the foils. Using the optional drawoptionlist element, the list of options can be embedded into the foil. 
        
        The foilgroup is required to have an options attribute which should be a perl list of possible options for the student.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
        <xs:element name="foilgroup" type="optionresponse--foilgroup"/>
        <xs:element ref="hintgroup"/>
        <xs:element ref="optionhint"/>
        <xs:element ref="hintpart"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="response-identification"/>
      <xs:attribute name="max" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            Max Number Of Shown Foils
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="yes" name="randomize" type="yesno-or-perl">
        <xs:annotation>
          <xs:documentation>
            Randomize Foil Order
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="horizontal" name="TeXlayout">
        <xs:annotation>
          <xs:documentation>
            Display of options when printed
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="horizontal"/>
                <xs:enumeration value="vertical"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:complexType name="optionresponse--foilgroup">
    <xs:annotation>
      <xs:documentation>Collection of Foils</xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded">
      <xs:element name="conceptgroup" type="optionresponse--conceptgroup"/>
      <xs:element name="foil" type="optionresponse--foil"/>
      <xs:element ref="comment"/>
    </xs:choice>
    <xs:attribute name="options" use="required">
      <xs:annotation>
        <xs:documentation>
          Perl list of possible foil values.
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:pattern value="@[a-zA-Z0-9_\-]+"/>
            </xs:restriction>
          </xs:simpleType>
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:pattern value="\(.+\)"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute name="texoptions">
      <xs:annotation>
        <xs:documentation>
          Print options
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="nochoice"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute name="checkboxvalue" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          Two-option checkboxes for
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="checkboxoptions">
      <xs:annotation>
        <xs:documentation>
          Checkbox options
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="nochoice"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType name="optionresponse--conceptgroup">
    <xs:annotation>
      <xs:documentation>
        Collection of similar foils.
        When a problem is displayed, only one of the contained foils is selected for display.
      </xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded">
      <xs:element name="foil" type="optionresponse--foil"/>
    </xs:choice>
    <xs:attribute name="concept" type="xs:string" use="required"/>
  </xs:complexType>
  <xs:complexType mixed="true" name="optionresponse--foil">
    <xs:annotation>
      <xs:documentation>
        Statement next to the drop-down box.
      </xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded" minOccurs="0">
      <xs:group ref="text-only"/>
      <xs:element ref="parserlib"/>
      <xs:element ref="scriptlib"/>
      <xs:element name="drawoptionlist" type="optionresponse--drawoptionlist"/>
    </xs:choice>
    <xs:attribute name="name" type="xs:string" use="required"/>
    <xs:attribute name="value" type="xs:string" use="required">
      <xs:annotation>
        <xs:documentation>
          Correct Option
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="random" name="location" type="location-or-perl"/>
  </xs:complexType>
  <xs:complexType name="optionresponse--drawoptionlist">
    <xs:annotation>
      <xs:documentation>Draw Option List</xs:documentation>
    </xs:annotation>
  </xs:complexType>
  
  <xs:annotation>
    <xs:documentation>
      Match response
    </xs:documentation>
  </xs:annotation>
  <xs:element name="matchresponse">
    <xs:annotation>
      <xs:documentation>
        Query for matches betweens items from two lists.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
        <xs:element name="foilgroup" type="matchresponse--foilgroup"/>
        <xs:element ref="hintgroup"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="response-identification"/>
      <xs:attribute name="max" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            Max Number Of Shown Foils
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="yes" name="randomize" type="yesno-or-perl">
        <xs:annotation>
          <xs:documentation>
            Randomize Foil Order
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:complexType name="matchresponse--itemgroup">
    <xs:annotation>
      <xs:documentation>Items to Match</xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded">
      <xs:element name="item" type="matchresponse--item"/>
    </xs:choice>
    <xs:attribute default="yes" name="randomize" type="yesno-or-perl">
      <xs:annotation>
        <xs:documentation>
          Randomize Order
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="top" name="location">
      <xs:annotation>
        <xs:documentation>
          Items Display Location
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="top"/>
              <xs:enumeration value="bottom"/>
              <xs:enumeration value="left"/>
              <xs:enumeration value="right"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute default="vertical" name="direction">
      <xs:annotation>
        <xs:documentation>
          Items Display Direction
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="vertical"/>
              <xs:enumeration value="horizontal"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute name="columns">
      <xs:annotation>
        <xs:documentation>
          Items Columns
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:int">
              <xs:enumeration value="1"/>
              <xs:enumeration value="2"/>
              <xs:enumeration value="3"/>
              <xs:enumeration value="4"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute name="TeXitemgroupwidth">
      <xs:annotation>
        <xs:documentation>
          TeXitemgroupwidth attribute allows you to specify the width of table with items for matching. The value of this attribute defines the width in percents with respect to text line width.
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:restriction base="xs:string">
          <xs:pattern value="\d+%"/>
        </xs:restriction>
      </xs:simpleType>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType mixed="true" name="matchresponse--item">
    <xs:annotation>
      <xs:documentation>Item</xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded" minOccurs="0">
      <xs:group ref="text-only"/>
      <xs:element ref="parserlib"/>
      <xs:element ref="scriptlib"/>
    </xs:choice>
    <xs:attribute name="name" type="xs:string"/>
    <xs:attribute default="random" name="location" type="location-or-perl"/>
  </xs:complexType>
  <xs:complexType name="matchresponse--foilgroup">
    <xs:annotation>
      <xs:documentation>Collection of Foils</xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded">
      <xs:element name="itemgroup" type="matchresponse--itemgroup"/>
      <xs:element name="conceptgroup" type="matchresponse--conceptgroup"/>
      <xs:element name="foil" type="matchresponse--foil"/>
      <xs:element ref="comment"/>
    </xs:choice>
  </xs:complexType>
  <xs:complexType name="matchresponse--conceptgroup">
    <xs:annotation>
      <xs:documentation>
        Collection of similar foils.
        When a problem is displayed, only one of the contained foils is selected for display.
      </xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded">
      <xs:element name="foil" type="matchresponse--foil"/>
    </xs:choice>
    <xs:attribute name="concept" type="xs:string" use="required"/>
  </xs:complexType>
  <xs:complexType mixed="true" name="matchresponse--foil">
    <xs:annotation>
      <xs:documentation>Foil</xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded" minOccurs="0">
      <xs:group ref="text-only"/>
      <xs:element ref="parserlib"/>
      <xs:element ref="scriptlib"/>
    </xs:choice>
    <xs:attribute name="name" type="xs:string" use="required"/>
    <xs:attribute name="value" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          Correct Option
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="random" name="location" type="location-or-perl"/>
  </xs:complexType>
  
  <xs:annotation>
    <xs:documentation>
      Rank response
    </xs:documentation>
  </xs:annotation>
  <xs:element name="rankresponse">
    <xs:annotation>
      <xs:documentation>
        Query to sort a list of items in the right order.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
        <xs:element name="foilgroup" type="rankresponse--foilgroup"/>
        <xs:element ref="hintgroup"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="response-identification"/>
      <xs:attribute name="max" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            Max Number Of Shown Foils
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="yes" name="randomize" type="yesno-or-perl">
        <xs:annotation>
          <xs:documentation>
            Randomize Foil Order
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:complexType name="rankresponse--foilgroup">
    <xs:annotation>
      <xs:documentation>Collection of Foils</xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded">
      <xs:element name="conceptgroup" type="rankresponse--conceptgroup"/>
      <xs:element name="foil" type="rankresponse--foil"/>
      <xs:element ref="comment"/>
    </xs:choice>
  </xs:complexType>
  <xs:complexType name="rankresponse--conceptgroup">
    <xs:annotation>
      <xs:documentation>
        Collection of similar foils.
        When a problem is displayed, only one of the contained foils is selected for display.
      </xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded">
      <xs:element name="foil" type="rankresponse--foil"/>
    </xs:choice>
    <xs:attribute name="concept" type="xs:string" use="required"/>
  </xs:complexType>
  <xs:complexType mixed="true" name="rankresponse--foil">
    <xs:annotation>
      <xs:documentation>Foil</xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded" minOccurs="0">
      <xs:group ref="text-only"/>
      <xs:element ref="parserlib"/>
      <xs:element ref="scriptlib"/>
    </xs:choice>
    <xs:attribute name="name" type="xs:string" use="required"/>
    <xs:attribute name="value" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          Rank Value
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="random" name="location" type="location-or-perl"/>
  </xs:complexType>
  
  <xs:annotation>
    <xs:documentation>
      Image response
    </xs:documentation>
  </xs:annotation>
  <xs:element name="imageresponse">
    <xs:annotation>
      <xs:documentation>
        Query for the selection of an image in a list.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
        <xs:element name="foilgroup" type="imageresponse--foilgroup"/>
        <xs:element ref="hintgroup"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="response-identification"/>
      <xs:attribute name="max" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            Max Number Of Shown Foils
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:complexType name="imageresponse--foilgroup">
    <xs:annotation>
      <xs:documentation>Collection of Imageresponse foils</xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded">
      <xs:element name="conceptgroup" type="imageresponse--conceptgroup"/>
      <xs:element name="foil" type="imageresponse--foil"/>
      <xs:element ref="comment"/>
    </xs:choice>
  </xs:complexType>
  <xs:complexType name="imageresponse--conceptgroup">
    <xs:annotation>
      <xs:documentation>
        Collection of similar foils.
        When a problem is displayed, only one of the contained foils is selected for display.
      </xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded">
      <xs:element name="foil" type="imageresponse--foil"/>
    </xs:choice>
    <xs:attribute name="concept" type="xs:string" use="required"/>
  </xs:complexType>
  <xs:complexType name="imageresponse--foil">
    <xs:annotation>
      <xs:documentation>Image response foil. image and rectangle are required.</xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded" minOccurs="0">
      <xs:element name="image" type="imageresponse--image"/>
      <xs:element name="polygon" type="imageresponse--polygon"/>
      <xs:element name="rectangle" type="imageresponse--rectangle"/>
      <xs:element name="text" type="imageresponse--text"/>
    </xs:choice>
    <xs:attribute name="name" type="xs:string" use="required"/>
  </xs:complexType>
  <xs:complexType mixed="true" name="imageresponse--image">
    <xs:annotation>
      <xs:documentation>
        Imageresponse Image (contains the image source file).
        
        The delimited text should correspond to a published image resource.
        Example: <image>/res/adm/includes/templates/man1.jpg</image>. The following image formats are recommended - gif, jpg or png. Other formats may work, but there may be printing or display issues. The image should only appear once per foil.
      </xs:documentation>
    </xs:annotation>
    <xs:choice minOccurs="0">
      <xs:element ref="gnuplot"/>
    </xs:choice>
  </xs:complexType>
  <xs:simpleType name="imageresponse--rectangle">
    <xs:annotation>
      <xs:documentation>
        Rectangular area in image (contains coordinate pairs).
        
        The delimited text specifies a rectangular area that is correct, specified as (x1,y1)-(x2,y2), where x1, x2, y1, and y2 are number corresponding to the x and y coordinates of two corners that define a rectangle which specifies where the right answer for this foil is located on the image. For example, (0,0)-(100,200) will specify that a rectangle 100 pixels wide and 200 pixels tall, situated in the upper left of the image, is correct. At least one rectangle is required; multiple rectangles may be specified.
      </xs:documentation>
    </xs:annotation>
    <xs:union memberTypes="perl">
      <xs:simpleType>
        <xs:restriction base="xs:string">
          <xs:pattern value="\s*\(.+\)-\(.+\)\s*"/>
        </xs:restriction>
      </xs:simpleType>
    </xs:union>
  </xs:simpleType>
  <xs:simpleType name="imageresponse--polygon">
    <xs:annotation>
      <xs:documentation>Polygonal area in image (contains coordinate list)</xs:documentation>
    </xs:annotation>
    <xs:union memberTypes="perl">
      <xs:simpleType>
        <xs:restriction base="xs:string">
          <xs:pattern value="\s*\(.+\)(-\(.+\))+\s*"/>
        </xs:restriction>
      </xs:simpleType>
    </xs:union>
  </xs:simpleType>
  <xs:complexType mixed="true" name="imageresponse--text">
    <xs:annotation>
      <xs:documentation>
        Text to describe option
        
        The delimited text is printed before the image is shown on the screen.
        This text is typically used to describe to the student what they are expected to click on.
      </xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded" minOccurs="0">
      <xs:group ref="text-only"/>
      <xs:element ref="parserlib"/>
      <xs:element ref="scriptlib"/>
    </xs:choice>
  </xs:complexType>
  
  <xs:annotation>
    <xs:documentation>
      Numerical response
    </xs:documentation>
  </xs:annotation>
  <xs:element name="numericalresponse">
    <xs:annotation>
      <xs:documentation>
        Query for one or several numbers, possibly with units.
        
        A tolerance parameter should be used to determine how closely the system will require the student’s answer to be in order to count it correct. The tolerance will default to zero if it is not defined. The tolerance parameter should always be defined for a numerical problem unless you are certain only integer answers are generated from your script and you want students to reply with exactly that integer.
        
        A significant figures parameter tells the system how many significant figures there are in the problem, as either a single number, e.g. 3, or a range of acceptable values, expressed as min,max. The system will check to make sure that the student’s answer contains this many significant digits, useful in many scientific calculations.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
        <xs:element name="answergroup" type="caparesponse--answergroup"/>
        <xs:element ref="hiddenline"/>
        <xs:element ref="hintgroup"/>
        <xs:element ref="numericalhint"/>
        <xs:element ref="hintpart"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="response-identification"/>
      <xs:attribute name="answer" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            The answer the system is looking for. The answer can use variables calculated/defined in the problem’s script block, allowing the answer to be determined dynamically (including randomization).
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="incorrect" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Incorrect Answers
            When switched into exam ("bubble sheet") mode, LON-CAPA usually create wrong answers automatically. To specify wrong answers yourself, you need to provide an array of incorrect values in this attribute. You need to provide at least as many incorrects as 1 less than the number of bubbles on the exam. You can provide more if you want to.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="unit" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Expected units for the answer. For instance, "m/s^2" or "km/(A*hr)".
            LON-CAPA will automatically perform some conversions between units of the same dimension when units are provided for a problem. You can provide an answer of "1.45 km" for a distance. If the computer expects the answer in cm, it will convert your answer before comparing against the numerical solution.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="format" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            You can format the number displayed by the computer as the answer. For instance, if the answer is one-third, the computer will display that it computed ".333333333" as the answer. If you'd like to shorten that, you can use the Format field. Format strings like "2E" (without the quotes) will display three significant digits in scientific notation. Format strings like "2f" will display two digits after the decimal point. Format strings like "2s" will round a number to 2 significant digits.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="preprocess" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Pre-Processor Subroutine
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:complexType name="caparesponse--answergroup">
    <xs:annotation>
      <xs:documentation>Collection of Answers</xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded">
      <xs:element name="answer" type="caparesponse--answer"/>
    </xs:choice>
    <xs:attribute default="ordered" name="type">
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="ordered"/>
              <xs:enumeration value="unordered"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType name="caparesponse--answer">
    <xs:annotation>
      <xs:documentation>
        Correct list of values or vectors.
      </xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded" minOccurs="0">
      <xs:element name="vector" type="caparesponse--vector"/>
      <xs:element name="value" type="caparesponse--value"/>
    </xs:choice>
    <xs:attribute name="name" type="xs:string"/>
    <xs:attribute default="ordered" name="type">
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="ordered"/>
              <xs:enumeration value="unordered"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
  </xs:complexType>
  <xs:simpleType name="caparesponse--value">
    <xs:annotation>
      <xs:documentation>Value</xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string"/>
  </xs:simpleType>
  <xs:simpleType name="caparesponse--vector">
    <xs:annotation>
      <xs:documentation>Vector</xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string"/>
  </xs:simpleType>
  <xs:annotation>
    <xs:documentation>
      Formula response (using caparesponse--answergroup like numericalresponse).
    </xs:documentation>
  </xs:annotation>
  <xs:element name="formularesponse">
    <xs:annotation>
      <xs:documentation>Query for a formula.</xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
        <xs:element name="answergroup" type="caparesponse--answergroup"/>
        <xs:element ref="hiddenline"/>
        <xs:element ref="hintgroup"/>
        <xs:element ref="formulahint"/>
        <xs:element ref="hintpart"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="response-identification"/>
      <xs:attribute name="answer" type="xs:string"/>
      <xs:attribute name="samples" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Sample Points.
            
            Format:
            1. A comma-separated list of the variables you wish to interpret,
            2. followed by “@” (not in quotes),
            3. followed by any number of the following two things, separated by semi-colons:
            (a) a comma-separated list of as many numbers as there are variables, which specifies one sampling point, OR
            (b) a comma-separated list of as many numbers as there are variables, followed by a colon, followed by another list of as many numbers as there are variables, followed by a #, followed by an integer.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="preprocess" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Pre-Processor Subroutine
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  
  <xs:annotation>
    <xs:documentation>
      Math response
    </xs:documentation>
  </xs:annotation>
  <xs:element name="mathresponse">
    <xs:annotation>
      <xs:documentation>
        Query for text that is evaluated with a script written in a computer algebra system language by the problem author.
        
        MathResponse is extremely powerful, as it tests answers for conditions rather than agreement with a particular correct answer. An unfortunate byproduct, however, is that it cannot be analyzed by several of the LON-CAPA statistics tools.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
        <xs:element name="answer" type="mathresponse--answer">
          <xs:annotation>
            <xs:documentation>
              Maxima or R script using the arrays RESPONSE and LONCAPALIST.
            </xs:documentation>
          </xs:annotation>
        </xs:element>
        <xs:element ref="textfield"/>
        <xs:element ref="hiddensubmission"/>
        <xs:element ref="hiddenline"/>
        <xs:element ref="hintgroup"/>
        <xs:element ref="mathhint"/>
        <xs:element ref="hintpart"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="response-identification"/>
      <xs:attribute name="answerdisplay" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            String to display for answer
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="cas" use="required">
        <xs:annotation>
          <xs:documentation>
            Algebra System. Maxima and R are supported.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="maxima"/>
                <xs:enumeration value="R"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="args" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Argument Array
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="libraries" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:complexType mixed="true" name="mathresponse--answer">
    <xs:annotation>
      <xs:documentation>Answer algorithm</xs:documentation>
    </xs:annotation>
    <xs:attribute name="type" type="xs:string"/>
    <xs:attribute fixed="preserve" ref="xml:space"/>
  </xs:complexType>
  
  <xs:annotation>
    <xs:documentation>
      Function plot response
    </xs:documentation>
  </xs:annotation>
  <xs:element name="functionplotresponse">
    <xs:annotation>
      <xs:documentation>
        Query for the drawing of a function.
        
        Requires that the student creates a plot that matches specified criteria.
        Examples can be functions that have certain slopes, curvature, maxima or minima at specified independent coordinate values. The students create their answer by dragging the curves and adjusting the slopes.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="functionplotelements"/>
        <xs:element ref="functionplotruleset"/>
        <xs:element ref="hintgroup"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="response-identification"/>
      <xs:attribute name="width" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            Width (pixels)
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="height" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            Height (pixels)
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="xlabel" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Label x-axis
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="-10" name="xmin" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Minimum x-value
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="10" name="xmax" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Maximum x-value
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="yes" name="xaxisvisible" type="yesno-or-perl">
        <xs:annotation>
          <xs:documentation>
            x-axis visible
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="ylabel" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Label y-axis
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="-10" name="ymin" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Minimum y-value
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="10" name="ymax" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Maximum y-value
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="yes" name="yaxisvisible" type="yesno-or-perl">
        <xs:annotation>
          <xs:documentation>
            y-axis visible
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="yes" name="gridvisible" type="yesno-or-perl">
        <xs:annotation>
          <xs:documentation>
            This determines whether or not the grid is on the graph.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="answerdisplay" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Background plot(s) for answer (function(x):xmin:xmax,function(x):xmin:xmax,x1:y1:sx1:sy1:x2:y2:sx2:sy2,...)
            
            This is a green curve the computer will display once the correct answer has been submitted. It is static, and can be given as a piecewise function.
            Since some problems will have multiple correct answers, this necessarily will only be a possible answer. Only the left hand side of the equation is necessary. For example, entering x + 2 will display the line y = x + 2.
            The syntax must be syntax recognized by GeoGebra. To test syntax for Geogebra directly, visit http://www.geogebra.org/webstart/geogebra.html .
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="functionplotelements">
    <xs:annotation>
      <xs:documentation>Function Plot Elements</xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="spline"/>
        <xs:element ref="backgroundplot"/>
        <xs:element ref="plotobject"/>
        <xs:element ref="plotvector"/>
        <xs:element ref="drawvectorsum"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="spline">
    <xs:annotation>
      <xs:documentation>
        At least one spline is necessary for a graph problem. These splines are what will be adjusted and analyzed to solve the problem.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="index">
        <xs:annotation>
          <xs:documentation>
            This is the label assigned to the spline. In general, it's simplest just to label them A, B, C etc.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:pattern value="[a-zA-Z_]+"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="order">
        <xs:annotation>
          <xs:documentation>
            This determines the number of Control Points on the spline. For example, selecting '3' means there will be 3 points on the spline that can be moved, as well as 3 points off the spline that will control the slope. 
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:int">
                <xs:minInclusive value="2"/>
                <xs:maxInclusive value="8"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="initx" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Initial x-value
            "Initial x-value" and "Initial y-value" determine where the left most Control Point will be.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="inity" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Initial y-value
            "Initial x-value" and "Initial y-value" determine where the left most Control Point will be.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="scalex" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            This determines the right most location of the Control Points (on the spline). To figure out where this point will be, add 'Initial x-value' to 'Scale x'.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="scaley" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            This determines the distance (in the y-direction) between the Control Points on the spline, and the ones that control the slope.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="backgroundplot">
    <xs:annotation>
      <xs:documentation>
        Background Function Plot.
        This places a static curve on the graph of your choosing. It can be labeled, moveable or fixed, and any color desired. Only the right hand side of the function you want displayed is necessary. For example, entering x+2 will display the line y=x+2. The syntax must be syntax recognized by GeoGebra.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="function" type="xs:string" use="required">
        <xs:annotation>
          <xs:documentation>
            An equals sign is not necessary. Just give the right hand side of the function. LON-CAPA variables are usable as well to allow individualized problems for each student. The syntax must be syntax recognized by GeoGebra.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="xinitial" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Initial x-value (optional)
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="xfinal" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Final x-value (optional)
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="label" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Label on Plot
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="color">
        <xs:annotation>
          <xs:documentation>
            Color of the background function (hex code).
            The default is 000000 (black). It is recommended to choose a color other than green, since it is easily confused with being the answer.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:pattern value="[\da-fA-F]{6}"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute default="yes" name="fixed" type="yesno-or-perl">
        <xs:annotation>
          <xs:documentation>
            Fixed location
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="plotobject">
    <xs:annotation>
      <xs:documentation>
        This places a point in the applet. Generally intended to be used with Vectors to create problems involving Free-Body Diagrams or any other points that vectors (or arrows) connect to and from.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="label" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Label on Plot
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="x" type="real-or-perl"/>
      <xs:attribute name="y" type="real-or-perl"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="plotvector">
    <xs:annotation>
      <xs:documentation>
        This creates a vector (or arrow) in the applet. Generally intended to be used with Objects to create problems involving Free-Body Diagrams or to establish connections between Objects.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="label" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Label on Plot.
            Determines the name of the vector, as well as the name that will be visible in the problem.
            This value MUST be capitalized and cannot include spaces or most symbols. To be safe, stick with letters and numbers.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="tailx" type="real-or-perl"/>
      <xs:attribute name="taily" type="real-or-perl"/>
      <xs:attribute name="tipx" type="real-or-perl"/>
      <xs:attribute name="tipy" type="real-or-perl"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="drawvectorsum">
    <xs:annotation>
      <xs:documentation>Draw Vector Sum</xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="label" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Label on Plot
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="tailx" type="real-or-perl"/>
      <xs:attribute name="taily" type="real-or-perl"/>
      <xs:attribute default="yes" name="showvalue" type="yesno-or-perl"/>
      <xs:attribute name="vectorlist" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="functionplotruleset">
    <xs:annotation>
      <xs:documentation>
        This is where the rules are defined. These rules will determine whether or not an entered answer is correct or not. If there are no rules, any answer will be deemed correct. If there is more than one rule, when an answer is submitted, the server will analyze them in order until one of them is broken (of course, if it's a correct answer, it will go through all of them and return a green box). In such an event, any subsequent rules will be ignored. If conditional hints related to these rules are added, only the first broken rule's hint will be shown, even if all rules are broken.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="functionplotrule"/>
        <xs:element ref="functionplotvectorrule"/>
        <xs:element ref="functionplotvectorsumrule"/>
        <xs:element ref="functionplotcustomrule"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="functionplotrule">
    <xs:annotation>
      <xs:documentation>
        Function Plot Graph Rule.
        
        Used to create a rule that determines whether or not a submitted graph is correct. In general, it takes the form of testing the function, its integral, or its first or second derivative over a given set of x-values. The test can be to see if it equals, is greater than, or less than a specified value. Anywhere a number is needed, a variable can also be used.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="index" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            This is an internal label for the rule. Something must be entered here, and it must be different for each rule. This same value will be used to add a conditional hint.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="0" name="derivativeorder">
        <xs:annotation>
          <xs:documentation>
            This determines what the server will be testing. For instance, choose 'First derivative' causes the server to evaluate the derivative of the entered answer over the given domain.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:int">
                <xs:enumeration value="0"/>
                <xs:enumeration value="1"/>
                <xs:enumeration value="2"/>
                <xs:enumeration value="-1"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="xinitial" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Initial x-value.
            A value must be entered for one of "Initial x-value" and "Initial x-value label". Either choose a numerical value for x (the first option), or choose the beginning of the submitted answer, the end, or a previously chosen named point.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="xinitiallabel">
        <xs:annotation>
          <xs:documentation>
            Initial x-value label.
            A value must be entered for one of "Initial x-value" and "Initial x-value label". Either choose a numerical value for x (the first option), or choose the beginning of the submitted answer, the end, or a previously chosen named point.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:pattern value="[a-zA-Z_]+"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="xfinal" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Final x-value (optional).
            This determines the end of the domain over which the rule examines. To test only a single point (the initial value), leave "Final x-value" and "Final x-value label" blank. If a label is entered, such as 'positive', the point at which the rule fails will be given this special label. This label can then be used in subsequent rules as an 'Initial x-value label'.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="xfinallabel">
        <xs:annotation>
          <xs:documentation>
            Final x-value label (optional).
            This determines the end of the domain over which the rule examines. To test only a single point (the initial value), leave "Final x-value" and "Final x-value label" blank. If a label is entered, such as 'positive', the point at which the rule fails will be given this special label. This label can then be used in subsequent rules as an 'Initial x-value label'.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:pattern value="[a-zA-Z_]+"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="minimumlength" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Minimum length for range (optional).
            This tests that the difference between the initial and final x-values are at least a certain length apart. This is only useful if there is at least one label.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="maximumlength" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Maximum length for range (optional).
            This tests that the difference between the initial and final x-values are at most a certain length apart. This is only useful if there is at least one label.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="eq" name="relationship">
        <xs:annotation>
          <xs:documentation>
            The heart of the rule. This choice determines whether the chosen 'function' is greater than, less than, equal to, etc. a certain 'value'.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="eq"/>
                <xs:enumeration value="ne"/>
                <xs:enumeration value="ge"/>
                <xs:enumeration value="gt"/>
                <xs:enumeration value="lt"/>
                <xs:enumeration value="le"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute default="undef" name="value" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Enter the number you wish to compare to. It is also possible to choose 'not defined', in the event the answer should not have a value for the given domain. Within the value argument, the function itself can be evaluated using &fpr_f(), its derivative using &fpr_dfdx(), and its second derivative using &fpr_d2fdx2(). This allows for a comparison of two points on the graph. The value of a previously defined label can be retrieved using the function &fpr_val(), e.g., &fpr_val('positive'). Previous defined values from script blocks can also be retrieved as normal variables, e.g., $x.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="percenterror" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            This allows for a margin of error in the y-direction. For instance, if the rule requires that the derivative be equal to 5, the server will accept values close enough to 5 that are within the percent error defined here. Note: Choosing 10% would not mean that the answer is correct as long as it is within the range 4.5-5.5. Instead, the percent corresponds to the total size of the graph. For the function itself, the 'percent error' is multiplied by the ymax-ymin; for the first derivative, it's multiplied by (ymax-ymin)/(xmax-xmin); for the second derivative, it's multiplied by (ymax-ymin)/(xmax-xmin)2; and for the integral, it's multiplied by (ymax-ymin)*(xmax-xmin).
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="functionplotvectorrule">
    <xs:annotation>
      <xs:documentation>
        Function Plot Vector Rule
        Used to test whether vectors are in the right place, pointed in the right direction, and have the correct length.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="index" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Index/Name
            This is an internal label for the rule. This attribute must be different for each rule. This same value will be used to add a conditional hint.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="vector" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            The name of one of the vectors in the list of function plot elements. Specifically, the one you want to test.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="attachpoint" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Attached to object.
            Object(s) this vector should be attached to. For more than one object, separate them by commas. If the vector should not be attached to any object, leave this blank. In this case, an object is considered attached if its tail OR its tip is in the vicinity of the object.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="notattachpoint" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Not attached to object.
            Object(s) that this vector should be not be near.
            For more than one object, separate them by commas. Particularly useful for distractor vectors.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="tailpoint" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Tail attached to object.
            Tail(s) this vector should be attached to.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="tippoint" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Tip attached to object.
            Tip(s) this vector should be attached to.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="nottailpoint" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Tail not attached to object.
            Tail(s) this vector should not be attached to.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="nottippoint" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Tip not attached to object.
            Tip(s) this vector should not be attached to.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="length" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            How long the vector should be.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="lengtherror" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Absolute error length.
            How accurate the length must be to get the answer correct.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="angle" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            What direction should the vector point. All values are measured in degrees, counterclockwise starting at the positive x-axis. Values must be 0 ≤ θ < 360.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="angleerror" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Absolute error angle.
            How accurate the angle must be to get the answer correct.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="functionplotvectorsumrule">
    <xs:annotation>
      <xs:documentation>
        Function Plot Vector Sum Rule
        Used to test the sum of a set of vectors.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="index" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Index/Name.
            This is an internal label for the rule. It must be different for each rule. This same value will be used to add a conditional hint.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="vectors" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Comma-separated list of vectors.
            List all of the vectors that should be added up to be tested.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="length" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Sum vector length.
            How long the sum of these vectors should be.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="lengtherror" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Absolute error length.
            How accurate the length must be to get the answer correct.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="angle" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Sum vector angle.
            What direction should the sum of these vectors point. All values are measured in degrees, counterclockwise starting at the positive x-axis. Values must be 0 ≤ θ < 360.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="angleerror" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Absolute error angle.
            How accurate the angle must be to get the answer correct.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="functionplotcustomrule">
    <xs:annotation>
      <xs:documentation>
        Used to create rules that aren't options using the other rules. The coding is done in Perl and follows Perl syntax. Any variable written inside this rule will be recognized as normal and any evaluation function can be used as well.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element name="answer">
          <xs:annotation>
            <xs:documentation>
              Answer algorithm, normally in Perl
            </xs:documentation>
          </xs:annotation>
          <xs:complexType mixed="true">
            <xs:attribute default="loncapa/perl" name="type" type="xs:string"/>
            <xs:attribute fixed="preserve" ref="xml:space"/>
          </xs:complexType>
        </xs:element>
      </xs:choice>
      <xs:attribute name="index" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Index/Name
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  
  <xs:annotation>
    <xs:documentation>
      Organic response
    </xs:documentation>
  </xs:annotation>
  <xs:element name="organicresponse">
    <xs:annotation>
      <xs:documentation>
        Query for an organic chemical structure with a molecular editor.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
        <xs:element ref="hiddenline"/>
        <xs:element ref="hintgroup"/>
        <xs:element ref="organichint"/>
        <xs:element ref="hintpart"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="response-identification"/>
      <xs:attribute default="autoez" name="options">
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:pattern value="(autoez|multipart|nostereo|reaction|number)(\s*,\s*(autoez|multipart|nostereo|reaction|number))*"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="molecule" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Starting Molecule
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="answer" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Correct Answer
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="jmeanswer" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            JME string of the answer
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="width" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            Width of correct answer image
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  
  <xs:annotation>
    <xs:documentation>
      Reaction response
    </xs:documentation>
  </xs:annotation>
  <xs:element name="reactionresponse">
    <xs:annotation>
      <xs:documentation>
        Query for a chemical reaction.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
        <xs:element ref="hiddenline"/>
        <xs:element ref="hintgroup"/>
        <xs:element ref="reactionhint"/>
        <xs:element ref="hintpart"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="response-identification"/>
      <xs:attribute name="answer" type="xs:string" use="required"/>
      <xs:attribute name="initial" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Initial Reaction
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  
  <xs:annotation>
    <xs:documentation>
      Custom response
    </xs:documentation>
  </xs:annotation>
  <xs:element name="customresponse">
    <xs:annotation>
      <xs:documentation>
        Query for text without any constraint (any character is allowed). A script analyzes the answer to grade it automatically.
        The use of this response type is generally discouraged, since the responses will not be analyzable by the LON-CAPA statistics tools.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
        <xs:element name="answer" type="customresponse--answer"/>
        <xs:element ref="textfield"/>
        <xs:element ref="hiddensubmission"/>
        <xs:element ref="hiddenline"/>
        <xs:element ref="hintgroup"/>
        <xs:element ref="customhint"/>
        <xs:element ref="hintpart"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="response-identification"/>
      <xs:attribute name="answerdisplay" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            String to display for answer
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:complexType mixed="true" name="customresponse--answer">
    <xs:annotation>
      <xs:documentation>
        Perl script evaluating the student answer.
        
        For a single textfield, the student’s answer will be in a variable $submission. If the Custom Response has multiple textfields, the answers will be in an array reference, and can be accessed as $$submission[0], $$submission[1], etc.
        
        The script must return a standard LON-CAPA response. The most common LON-CAPA responses are:
        - EXACT ANS: return if solved exactly correctly
        - APPROX ANS: return if solved approximately
        - INCORRECT: return if not correct, uses up a try
        - ASSIGNED SCORE: partial credit (also return the credit factor, e.g. return(ASSIGNED SCORE,0.3);)
        - SIG FAIL, NO UNIT, EXTRA ANSWER, MISSING ANSWER, BAD FORMULA, WANTED NUMERIC, WRONG FORMAT: return if not correct for different reasons, does not use up a try
      </xs:documentation>
    </xs:annotation>
    <xs:attribute name="type" type="xs:string"/>
    <xs:attribute fixed="preserve" ref="xml:space"/>
  </xs:complexType>
  
  <xs:annotation>
    <xs:documentation>
      External response
    </xs:documentation>
  </xs:annotation>
  <xs:element name="externalresponse">
    <xs:annotation>
      <xs:documentation>
        Query for a long text or a line, sent to an external program for grading.
        
        The form sent will consist of:
        - LONCAPA student response full text of what the student entered in the entry field
        - LONCAPA correct answer contents of the answer attribute
        - LONCAPA language specified language encoding of the requesting resource
        - all items in the form attribute if any of these clash with the above, the above values will overwite the value in the form attribute
        
        The response of the remote server needs to be in XML as follows:
        - loncapagrade: takes no attributes, but must surround the response.
        - awarddetail: required. The delimited text inside must be one of the detailed results that appears in the data storage documentation. CVS:loncapa/doc/homework/datastorage, look for resource.partid.responseid.awarddetail.
        - message: optional message to have shown to the student.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="textfield"/>
        <xs:element ref="hiddensubmission"/>
        <xs:element ref="hiddenline"/>
        <xs:element ref="hintgroup"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="response-identification"/>
      <xs:attribute name="url" type="xs:anyURI">
        <xs:annotation>
          <xs:documentation>
            url to submit the answer form to. It does not need to be a LON-CAPA machine.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="answer" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            data to post in the form element LONCAPA_correct_answer to the remote site.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="form" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            hash variable name that will be submitted to the remote site as a HTTP form.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="answerdisplay" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  
  <xs:annotation>
    <xs:documentation>
      Data response
    </xs:documentation>
  </xs:annotation>
  <xs:element name="dataresponse">
    <xs:annotation>
      <xs:documentation>
        Query for text or numbers.
        Advanced type of response that implements a simple data storage and needs an input tag, such as textline, to work correctly.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="textfield"/>
        <xs:element ref="hiddensubmission"/>
        <xs:element ref="hiddenline"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="response-identification"/>
      <xs:attribute name="type" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            type of data stored in this response field. It should be one of the types supported by parameter.html
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="display" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            string that will be used to describe the field when interfacing with humans.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  
  <xs:annotation>
    <xs:documentation>
      Shared response elements
    </xs:documentation>
  </xs:annotation>
  <xs:element name="responseparam">
    <xs:annotation>
      <xs:documentation>
        Parameters for a response
        
        Defines an externally adjustable parameter for the question, which the question can then use to allow other users to customize the problem for their courses without changing the source code of the problem.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="name" type="xs:string"/>
      <xs:attribute name="type" type="xs:string" use="required"/>
      <xs:attribute name="default" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            default value
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="description" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="textfield">
    <xs:annotation>
      <xs:documentation>
        Large Text Entry Area, contains the text that appears by default
        
        Creates a large text input box. If data appears between the start and end tags, the data will appear in the textfield if the student has not yet made a submission.
        Additionally, it takes two attributes: rows and cols, which control the height and width of the text area respectively.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="tex"/>
        <xs:element ref="web"/>
      </xs:choice>
      <xs:attribute default="10" name="rows" type="int-or-perl"/>
      <xs:attribute default="80" name="cols" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            Columns
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="addchars" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Click-On Texts (comma sep)
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="spellcheck">
        <xs:annotation>
          <xs:documentation>
            Spellcheck for
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="none"/>
                <xs:enumeration value="en"/>
                <xs:enumeration value="de"/>
                <xs:enumeration value="he"/>
                <xs:enumeration value="es"/>
                <xs:enumeration value="fr"/>
                <xs:enumeration value="pt"/>
                <xs:enumeration value="tr"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="textline">
    <xs:annotation>
      <xs:documentation>
        Single Line Text Entry Area. Displays a field to enter text for a response.
        Should only be used inside stringresponse, numericalresponse, formularesponse, mathresponse, organicresponse, reactionresponse and customresponse.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute default="20" name="size" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            controls the width of the textline
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="addchars" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Comma-separated list of characters or words that can be inserted with a click.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="no" name="readonly" type="yesno-or-perl"/>
      <xs:attribute name="spellcheck">
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="none"/>
                <xs:enumeration value="en"/>
                <xs:enumeration value="de"/>
                <xs:enumeration value="he"/>
                <xs:enumeration value="es"/>
                <xs:enumeration value="fr"/>
                <xs:enumeration value="pt"/>
                <xs:enumeration value="tr"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="hiddensubmission">
    <xs:annotation>
      <xs:documentation>
        This creates a hidden form field with the given value.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="value" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="hiddenline">
    <xs:annotation>
      <xs:documentation>
        This creates a hidden form field with the old response value.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
    </xs:complexType>
  </xs:element>
  
  <xs:annotation>
    <xs:documentation>
      Hints
    </xs:documentation>
  </xs:annotation>
  <xs:element name="hintgroup">
    <xs:annotation>
      <xs:documentation>
        The first part of the hint is the condition, which includes a specification of the foil(s) and foil answer(s) required to trigger the hint. The answers specified in the hint condition are compared with the user's submission, and if the condition is met, the hint action included in the conditional hint block will be executed (for example this could be the display of a block of text). You can set multiple hint conditions for a particular problem. Hint conditions are identified by a name. The corresponding hint action includes this hint condition name in the "on" parameter. When a hint condition evaluates to true, the corresponding hint action is triggered. Besides providing hint actions within <hintpart on="NAME"> </hintpart> tags for each named (NAME) hint condition, a hint can be designated for display if none of the conditional hints evaluate to true. The default hint is not displayed if the conditions were met for any of the conditional hints. The defau!
 lt hint action is included between <hintpart on="default"> </hintpart> tags.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
        <xs:element ref="hintpart"/>
        <xs:element ref="stringhint"/>
        <xs:element ref="radiobuttonhint"/>
        <xs:element ref="optionhint"/>
        <xs:element ref="numericalhint"/>
        <xs:element ref="formulahint"/>
        <xs:element ref="mathhint"/>
        <xs:element ref="organichint"/>
        <xs:element ref="reactionhint"/>
        <xs:element ref="customhint"/>
      </xs:choice>
      <xs:attribute default="no" name="showoncorrect" type="yesno-or-perl">
        <xs:annotation>
          <xs:documentation>
            Show hint even if problem Correct
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="hintpart">
    <xs:annotation>
      <xs:documentation>
        Conditional Hint
        
        When a hint tag named the same as the on attribute evaluates to be correct, the hintpart will show.
        If no other hintpart is to show then all hintparts with an on value set to “default” will show.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
        <xs:element ref="parserlib"/>
        <xs:element ref="scriptlib"/>
      </xs:choice>
      <xs:attribute name="on" type="xs:string" use="required"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="stringhint">
    <xs:annotation>
      <xs:documentation>
        String Hint Condition
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
      </xs:choice>
      <xs:attribute name="id" type="xs:string"/>
      <xs:attribute name="name" type="xs:string" use="required">
        <xs:annotation>
          <xs:documentation>
            Name of the hint condition.
            Should be set to the value of which hintpart will be shown.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="answer" type="xs:string" use="required">
        <xs:annotation>
          <xs:documentation>
            Text string.
            Should be set to the value of which hintpart will be shown.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="cs" name="type">
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="cs"/>
                <xs:enumeration value="ci"/>
                <xs:enumeration value="mc"/>
                <xs:enumeration value="re"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="preprocess" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Pre-Processor Subroutine
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="radiobuttonhint">
    <xs:annotation>
      <xs:documentation>
        Radiobutton Hint Condition
        
        The radiobutton hint tag takes two parameters: answer and name. The name is the name of the hint condition, and the answer is an array. The first element of the array will be 'foil'; the remaining elements are the names of the foils that you require to have been checked by the student for the hint to be displayed. For example, if you create a radiobutton response problem with six foils named: granite, gabbro, gneiss, shale, sandstone and schist, and you want your hint named: igneous to be displayed when either granite or basalt had been checked your radiobutton hint would be as follows:
        
        <radiobuttonhint answer="('foil','granite','gabbro')" name="igneous"></radiobuttonhint>
        
        In order to trigger display of this hint you also need to create a <hintpart> </hintpart> block that will include a textblock that contains the text of the actual hint.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
      </xs:choice>
      <xs:attribute name="id" type="xs:string"/>
      <xs:attribute name="name" type="xs:string" use="required">
        <xs:annotation>
          <xs:documentation>
            should be set to the value of which hintpart will be shown
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="answer" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            should be at least a two element list: first the type (foil or concept) and then either the foil name(s) or the concept string(s), e.g., “(’foil’,’greaterthan’,’equal’)” if the condition should be triggered by the foils named “greaterthan” or “equal”
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="optionhint">
    <xs:annotation>
      <xs:documentation>
        Option Response Hint Condition
        
        There are two types of option response hint conditions: one for standalone foils and one for concept groups. In both cases the option hint tag includes two parameters: answer and name for standalone foils, and concept and name for foils grouped together in a concept group. For the answer parameter, the names and submitted values for each of the foils that are being included in the hint condition are provided in a hash, i.e., in the format: ('Foil1'= > 'True','Foil2'= > 'False'). In the case of a conditional hint for a concept group, the format of the concept parameter is also a hash that links the name of each concept group included in the hint condition to either 'correct' or 'incorrect' - e.g., < optionhint concept="('buoyancy'= > 'correct','density'= > 'correct')" name="fluids" / > If 'correct' is specified for a named concept then when the conditional hint is evaluated answers for each of the foils selected by a student must be correct for the h!
 int action to be triggered. If anything other than 'correct' is provided in the concept hash in the optionhint tag then then students answers will be compared with the set answers for the foils in the concept group and as long as at least one answer is incorrect (i.e., the concept group was not correctly answered) then the corresponding hint action will be triggered. 
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
      </xs:choice>
      <xs:attribute name="id" type="xs:string"/>
      <xs:attribute name="name" type="xs:string" use="required">
        <xs:annotation>
          <xs:documentation>
            should be set to the value of which hintpart will be shown
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="answer" type="xs:string"/>
      <xs:attribute name="concept" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="numericalhint">
    <xs:annotation>
      <xs:documentation>
        Numerical Hint Condition
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
      </xs:choice>
      <xs:attribute name="id" type="xs:string"/>
      <xs:attribute name="name" type="xs:string" use="required">
        <xs:annotation>
          <xs:documentation>
            Unique name given to the hint condition.
            Should be set to the value of which hintpart will be shown.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="answer" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Numerical answer for which the conditional is provided.
            Student submission of that answer in combination with the "unit" attribute in the hint condition will trigger the hint action specified in the <hintpart> element.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="unit" type="xs:string"/>
      <xs:attribute name="format" type="xs:string"/>
      <xs:attribute name="preprocess" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Pre-Processor Subroutine
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="formulahint">
    <xs:annotation>
      <xs:documentation>
        Formula Hint Condition
        
        The formula submitted by the student is evaluated at the sample points for the hint and the calculated values are compared with the corresponding values determined by evaluating the "hint" answer at the same sampling points. A close correspondence between the two sets of values will trigger the hint action specified in the <hintpart> element.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
      </xs:choice>
      <xs:attribute name="id" type="xs:string"/>
      <xs:attribute name="name" type="xs:string" use="required">
        <xs:annotation>
          <xs:documentation>
            Unique name given to the hint condition.
            Should be set to the value of which hintpart will be shown.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="answer" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Formula answer for which the conditional is provided.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="samples" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Sample points (or range of points) over which sampling of the student’s submitted answer and the formula included in the formula hint answer parameter are to be compared. The syntax is the same as used to specify sampling points in the samples
            parameter of the formula reponse element itself.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="preprocess" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Pre-Processor Subroutine
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="mathhint">
    <xs:annotation>
      <xs:documentation>Math Hint Condition</xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
        <xs:element name="answer" type="mathhint--answer"/>
      </xs:choice>
      <xs:attribute name="id" type="xs:string"/>
      <xs:attribute name="name" type="xs:string"/>
      <xs:attribute default="maxima" name="cas">
        <xs:annotation>
          <xs:documentation>
            Algebra System
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="maxima"/>
                <xs:enumeration value="R"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="args" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Argument Array
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="organichint">
    <xs:annotation>
      <xs:documentation>Organic Hint Condition</xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
      </xs:choice>
      <xs:attribute name="id" type="xs:string"/>
      <xs:attribute name="name" type="xs:string"/>
      <xs:attribute name="answer" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="reactionhint">
    <xs:annotation>
      <xs:documentation>Reaction Hint Condition</xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
      </xs:choice>
      <xs:attribute name="id" type="xs:string"/>
      <xs:attribute name="name" type="xs:string"/>
      <xs:attribute name="answer" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="customhint">
    <xs:annotation>
      <xs:documentation>
        Custom Hint Condition
        
        Define the hint condition within an answer block inside of the customhint block. The condition is defined like how an answer is defined in customresponse where you need to return EXACT ANS to indicate when customhint criteria are met.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
        <xs:element name="answer" type="customhint--answer"/>
      </xs:choice>
      <xs:attribute name="id" type="xs:string"/>
      <xs:attribute name="name" type="xs:string" use="required">
        <xs:annotation>
          <xs:documentation>
            should be set to the value of which hintpart will be shown
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:complexType mixed="true" name="customhint--answer">
    <xs:annotation>
      <xs:documentation>Hint algorithm</xs:documentation>
    </xs:annotation>
    <xs:attribute name="type" type="xs:string"/>
    <xs:attribute fixed="preserve" ref="xml:space"/>
  </xs:complexType>
  <xs:complexType mixed="true" name="mathhint--answer">
    <xs:annotation>
      <xs:documentation>Hint algorithm</xs:documentation>
    </xs:annotation>
    <xs:attribute name="type" type="xs:string"/>
    <xs:attribute fixed="preserve" ref="xml:space"/>
  </xs:complexType>
  
  <xs:annotation>
    <xs:documentation>
      Random label
    </xs:documentation>
  </xs:annotation>
  <xs:element name="randomlabel">
    <xs:annotation>
      <xs:documentation>
        Randomly labeled image
        
        This shows a specified image with images or text labels randomly assigned to a set of specific locations. Those locations may also have values assigned to them. A hash is generated that contains the mapping of labels to locations, labels to values, and locations to values.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element name="labelgroup" type="randomlabel--labelgroup">
          <xs:annotation>
            <xs:documentation>
              One is required, but multiple are allowed. This declares a group of locations and labels associated with them.
            </xs:documentation>
          </xs:annotation>
        </xs:element>
        <xs:element name="bgimg">
          <xs:annotation>
            <xs:documentation>
              Element alternative to the bgimg attribute, which makes it possible to use a plot as a background image.
            </xs:documentation>
          </xs:annotation>
          <xs:complexType mixed="true">
            <xs:choice maxOccurs="unbounded" minOccurs="0">
              <xs:element ref="gnuplot"/>
            </xs:choice>
          </xs:complexType>
        </xs:element>
      </xs:choice>
      <xs:attribute name="bgimg" type="xs:anyURI">
        <xs:annotation>
          <xs:documentation>
            Either a fully qualified URL for an external image or a LON-CAPA resource. It supports relative references (../images/apicture.gif). The image must either be a GIF or JPEG.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="width" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            The width of the image in pixels.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="height" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            The height of the image in pixels.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="texwidth" type="decimal-or-perl">
        <xs:annotation>
          <xs:documentation>
            The width of the image in millimeters.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:complexType name="randomlabel--labelgroup">
    <xs:annotation>
      <xs:documentation>Group of Labels</xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded">
      <xs:element name="label" type="randomlabel--label"/>
      <xs:element name="location" type="randomlabel--location">
        <xs:annotation>
          <xs:documentation>
            declares a location on the image that a label should appear at
          </xs:documentation>
        </xs:annotation>
      </xs:element>
    </xs:choice>
    <xs:attribute name="name" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          This is the name of the group.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="text" name="type">
      <xs:annotation>
        <xs:documentation>
          the type of labels in this group
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="text"/>
              <xs:enumeration value="image"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute default="\normalsize" name="TeXsize">
      <xs:annotation>
        <xs:documentation>
          TeX font size
          Warning: as opposed to the TeXsize attribute in <h1>..<h6> <font> and <basefont>, this one requires a \ at the beginning of the values.
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="\tiny"/>
              <xs:enumeration value="\scriptsize"/>
              <xs:enumeration value="\footnotesize"/>
              <xs:enumeration value="\small"/>
              <xs:enumeration value="\normalsize"/>
              <xs:enumeration value="\large"/>
              <xs:enumeration value="\Large"/>
              <xs:enumeration value="\LARGE"/>
              <xs:enumeration value="\huge"/>
              <xs:enumeration value="\Huge"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType mixed="true" name="randomlabel--label">
    <xs:annotation>
      <xs:documentation>Label Text or Path to image</xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded" minOccurs="0">
      <xs:group ref="text-only"/>
      <xs:element ref="preduedate"/>
      <xs:element ref="postanswerdate"/>
      <xs:element ref="parserlib"/>
      <xs:element ref="scriptlib"/>
    </xs:choice>
    <xs:attribute name="description" type="xs:string"/>
  </xs:complexType>
  <xs:complexType name="randomlabel--location">
    <xs:annotation>
      <xs:documentation>Label Location</xs:documentation>
    </xs:annotation>
    <xs:attribute name="x" type="int-or-perl" use="required"/>
    <xs:attribute name="y" type="int-or-perl" use="required"/>
    <xs:attribute name="value" type="xs:string"/>
  </xs:complexType>
  
  <xs:annotation>
    <xs:documentation>
      Gnuplot
    </xs:documentation>
  </xs:annotation>
  <xs:element name="gnuplot">
    <xs:annotation>
      <xs:documentation>
        The gnuplot LON-CAPA tag allows an author to design a plot which will be created programatically at the time when it is requested for display by a student. This is intended for use in homework problems where a distinct plot should be rendered for each student. It can be used in conjunction with a script to generate curve data for random plots.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="gnuplot-children"/>
      </xs:choice>
      <xs:attribute default="dynamically generated plot" name="alttag" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Brief description of the plot.
            This text is used as the alt value of the img tag used to display the plot on a web page.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="300" name="height" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            Height of image (pixels)
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="400" name="width" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            Width of image (pixels)
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="xffffff" name="bgcolor" type="color-or-perl">
        <xs:annotation>
          <xs:documentation>
            Background color of image
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="x000000" name="fgcolor" type="color-or-perl">
        <xs:annotation>
          <xs:documentation>
            Foreground color of image
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="off" name="transparent" type="onoff-or-perl">
        <xs:annotation>
          <xs:documentation>
            Transparent image. If the image is transparent the background color will be ignored.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="on" name="grid" type="onoff-or-perl">
        <xs:annotation>
          <xs:documentation>
            Display grid
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="off" name="gridlayer" type="onoff-or-perl">
        <xs:annotation>
          <xs:documentation>
            Display grid front layer over filled boxes or filled curves
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="noborder" name="box_border">
        <xs:annotation>
          <xs:documentation>
            Draw border for boxes
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="border"/>
                <xs:enumeration value="noborder"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute default="on" name="border" type="onoff-or-perl">
        <xs:annotation>
          <xs:documentation>
            Draw border around plot
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="9" name="font">
        <xs:annotation>
          <xs:documentation>
            Font size to use in web output (in pts, or "small", "medium" or "large").
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:int">
                <xs:minInclusive value="5"/>
                <xs:maxInclusive value="15"/>
              </xs:restriction>
            </xs:simpleType>
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="small"/>
                <xs:enumeration value="medium"/>
                <xs:enumeration value="large"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute default="sans-serif" name="fontface">
        <xs:annotation>
          <xs:documentation>
            Type of font to use
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="sans-serif"/>
                <xs:enumeration value="serif"/>
                <xs:enumeration value="classic"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute default="100" name="samples">
        <xs:annotation>
          <xs:documentation>
            Number of samples for non-data plots.
            If a function element is used to specify the curve, this indicates the number of sample points to use.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:int">
                <xs:minInclusive value="100"/>
                <xs:maxInclusive value="5000"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute default="middle" name="align">
        <xs:annotation>
          <xs:documentation>
            Alignment for image in HTML
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="left"/>
                <xs:enumeration value="right"/>
                <xs:enumeration value="middle"/>
                <xs:enumeration value="center"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute default="93" name="texwidth" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            Width of plot when printed (mm)
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="22" name="texfont">
        <xs:annotation>
          <xs:documentation>
            Font size to use in TeX output (pts)
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:int">
                <xs:minInclusive value="8"/>
                <xs:maxInclusive value="36"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute default="monochrome" name="plotcolor">
        <xs:annotation>
          <xs:documentation>
            Color setting for printing
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="monochrome"/>
                <xs:enumeration value="color"/>
                <xs:enumeration value="colour"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="pattern">
        <xs:annotation>
          <xs:documentation>
            Pattern value for boxes
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:int">
                <xs:minInclusive value="0"/>
                <xs:maxInclusive value="6"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute default="0" name="solid" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            The density of fill style for boxes
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="empty" name="fillstyle">
        <xs:annotation>
          <xs:documentation>
            Filled style for boxes
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="empty"/>
                <xs:enumeration value="solid"/>
                <xs:enumeration value="pattern"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute default="Cartesian" name="plottype">
        <xs:annotation>
          <xs:documentation>
            Plot type
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="Cartesian"/>
                <xs:enumeration value="Polar"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute default="Cartesian" name="gridtype">
        <xs:annotation>
          <xs:documentation>
            Grid type
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="Cartesian"/>
                <xs:enumeration value="Polar"/>
                <xs:enumeration value="Linear-Log"/>
                <xs:enumeration value="Log-Linear"/>
                <xs:enumeration value="Log-Log"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute default="default" name="lmargin">
        <xs:annotation>
          <xs:documentation>
            Left margin width (pts)
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl xs:int">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="default"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute default="default" name="rmargin">
        <xs:annotation>
          <xs:documentation>
            Right margin width (pts)
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl xs:int">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="default"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute default="default" name="tmargin">
        <xs:annotation>
          <xs:documentation>
            Top margin width (pts)
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl xs:int">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="default"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute default="default" name="bmargin">
        <xs:annotation>
          <xs:documentation>
            Bottom margin width (pts)
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl xs:int">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="default"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="boxwidth" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Width of boxes, default is auto
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="1" name="major_ticscale" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Size of major tic marks (plot coordinates)
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="0.5" name="minor_ticscale" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Size of minor tic mark (plot coordinates)
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:group name="gnuplot-children">
    <xs:annotation>
      <xs:documentation>
        List of children, used in gnuplot and lonplot--block
      </xs:documentation>
    </xs:annotation>
    <xs:choice>
      <xs:element name="title" type="lonplot--title"/>
      <xs:element name="axis" type="lonplot--axis">
        <xs:annotation>
          <xs:documentation>
            The Plot Axes tag allows you to specify the domain and range of the data to display. It is closely tied with the Plot Ticks tags, which specify where the gridlines are drawn on the plot.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="curve" type="lonplot--curve">
        <xs:annotation>
          <xs:documentation>
            The curve tag is where you set the data to be plotted by gnuplot.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="key" type="lonplot--key">
        <xs:annotation>
          <xs:documentation>
            The key tag causes a key to be drawn on the plot when it is generated. The key will contain an entry for each curve which has a name.
            The key is the color of the foreground of the plot, specified in the gnuplot tag.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="label" type="lonplot--label">
        <xs:annotation>
          <xs:documentation>
            The label tag allows the author to place text at any position on the plot. There may be many label tags on one plot and all the labels which fall within the plot will show. The color used will be to foreground color of the plot and the font will be the size specified for the plot, both of which are set in the gnuplot tag.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="xtics" type="lonplot--xtics"/>
      <xs:element name="ytics" type="lonplot--ytics"/>
      <xs:element name="xlabel" type="lonplot--xlabel"/>
      <xs:element name="ylabel" type="lonplot--ylabel"/>
      <xs:element name="block" type="lonplot--block"/>
    </xs:choice>
  </xs:group>
  <xs:simpleType name="lonplot--title">
    <xs:annotation>
      <xs:documentation>Plot Title</xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string"/>
  </xs:simpleType>
  <xs:complexType name="lonplot--axis">
    <xs:annotation>
      <xs:documentation>Plot axes</xs:documentation>
    </xs:annotation>
    <xs:attribute default="x000000" name="color" type="color-or-perl">
      <xs:annotation>
        <xs:documentation>
          Color of grid lines
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="-10.0" name="xmin" type="real-or-perl">
      <xs:annotation>
        <xs:documentation>
          Minimum x-value shown in plot
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="10.0" name="xmax" type="real-or-perl">
      <xs:annotation>
        <xs:documentation>
          Maximum x-value shown in plot
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="-10.0" name="ymin" type="real-or-perl">
      <xs:annotation>
        <xs:documentation>
          Minimum y-value shown in plot
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="10.0" name="ymax" type="real-or-perl">
      <xs:annotation>
        <xs:documentation>
          Maximum y-value shown in plot
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="on" name="xformat">
      <xs:annotation>
        <xs:documentation>
          X-axis number formatting
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="on"/>
              <xs:enumeration value="off"/>
              <xs:enumeration value="2e"/>
              <xs:enumeration value="2f"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute default="on" name="yformat">
      <xs:annotation>
        <xs:documentation>
          Y-axis number formatting
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="on"/>
              <xs:enumeration value="off"/>
              <xs:enumeration value="2e"/>
              <xs:enumeration value="2f"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute default="off" name="xzero">
      <xs:annotation>
        <xs:documentation>
          Show x-zero (y=0) axis
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="off"/>
              <xs:enumeration value="line"/>
              <xs:enumeration value="thick-line"/>
              <xs:enumeration value="dotted"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute default="off" name="yzero">
      <xs:annotation>
        <xs:documentation>
          Show y-zero (x=0) axis
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="off"/>
              <xs:enumeration value="line"/>
              <xs:enumeration value="thick-line"/>
              <xs:enumeration value="dotted"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType name="lonplot--curve">
    <xs:annotation>
      <xs:documentation>Plot Curve</xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded" minOccurs="0">
      <xs:element name="data" type="lonplot--data"/>
      <xs:element name="function" type="lonplot--function"/>
    </xs:choice>
    <xs:attribute default="x000000" name="color" type="color-or-perl">
      <xs:annotation>
        <xs:documentation>
          Color of curve
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="name" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          Name of curve to appear in key
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="lines" name="linestyle">
      <xs:annotation>
        <xs:documentation>
          Unless otherwise noted the linestyles require only 2 data sets, X and Y.
          
          - lines: Connect adjacent points with straight line segments.
          - points: Display a small marker at each point.
          - linespoints: Draw both lines and points.
          Draws a small symbol at each point and then connects adjacent points with straight line segments.
          - dots: Place a tiny dots on the given points.
          - steps: Connect points with horizontal lines.
          This style connects consecutive points with two line segments: the first from (x1,y1) to (x2,y1) and the second from (x2,y1) to (x2,y2).
          - fsteps: Connect data with horizontal lines.
          This style connects consecutive points with two line segments: the first from (x1,y1) to (x1,y2) and the second from (x1,y2) to (x2,y2).
          - histeps: Plot as histogram.
          Y-values are assumed to be centered at the x-values; the point at x1 is represented as a horizontal line from ((x0+x1)/2,y1) to ((x1+x2)/2,y1). The lines representing the end points are extended so that the step is centered on at x. Adjacent points are connected by a vertical line at their average x, that is, from ((x1+x2)/2,y1) to ((x1+x2)/2,y2).
          - errorbars: Same as yerrorbars.
          - xerrorbars: Draw horizontal error bars around the points.
          Requires 3 or 4 data sets. Either X, Y, Xdelta or X, Y, Xlower, Xupper. Xdelta is a change relative to the given X value. The Xlower and Xupper values are absolute grid coordinates of the upper and lower values to indicated with error bars.
          - yerrorbars: Draw vertical error bars around the points.
          Requires 3 or 4 data sets. Either X, Y, Ydelta or X, Y, Ylower, Yupper. Ydelta is a change relative to the given Y value. The Ylower and Yupper values are the grid coordinates of the upper and lower values to indicate with error bars.
          - xyerrorbars: Draw both vertical and horizontal error bars around the points.
          Requires 4 or 6 data sets. Either X, Y, Xdelta, Ydelta or X, Y, Xlower, Xupper, Ylower, Yupper. Xdelta and Ydelta are relative to the given coordinates. Xlower, Xupper, Ylower, and Yupper are the grid coordinates of the upper and lower values to indicate with the error bars.
          - boxes: Draw a box from the X-axis to the Y-value given.
          Requires either 2 or 3 data sets. Either X, Y or X, Y, Xwidth. In the first case the boxes will be drawn next to eachother. In the latter case Xwidth indicates the horizontal width of the box for the given coordinate.
          - vector: Draws a vector field based on the given data.
          Requires 4 data sets, X, Y, Xdelta, and Ydelta. The ‘vector‘ style draws a vector from (X,Y) to (X+Xdelta,Y+Ydelta). It also draws a small arrowhead at the end of the vector. May not be fully supported by gnuplot.
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="lines"/>
              <xs:enumeration value="linespoints"/>
              <xs:enumeration value="dots"/>
              <xs:enumeration value="points"/>
              <xs:enumeration value="steps"/>
              <xs:enumeration value="fsteps"/>
              <xs:enumeration value="histeps"/>
              <xs:enumeration value="errorbars"/>
              <xs:enumeration value="xerrorbars"/>
              <xs:enumeration value="yerrorbars"/>
              <xs:enumeration value="xyerrorbars"/>
              <xs:enumeration value="boxes"/>
              <xs:enumeration value="filledcurves"/>
              <xs:enumeration value="vector"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute default="1" name="linewidth">
      <xs:annotation>
        <xs:documentation>
          Line width (may not apply to all plot styles)
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:int">
              <xs:minInclusive value="1"/>
              <xs:maxInclusive value="10"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute default="solid" name="linetype">
      <xs:annotation>
        <xs:documentation>
          Line type (may not apply to all plot styles)
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="solid"/>
              <xs:enumeration value="dashed"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute default="1" name="pointsize" type="int-or-perl">
      <xs:annotation>
        <xs:documentation>
          Point size (may not apply to all plot styles)
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="1" name="pointtype">
      <xs:annotation>
        <xs:documentation>
          Point type (may not apply to all plot styles)
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:int">
              <xs:minInclusive value="0"/>
              <xs:maxInclusive value="6"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute default="closed" name="limit">
      <xs:annotation>
        <xs:documentation>
          Point to fill for filled curves
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="above"/>
              <xs:enumeration value="below"/>
              <xs:enumeration value="closed"/>
              <xs:enumeration value="x1"/>
              <xs:enumeration value="x2"/>
              <xs:enumeration value="y1"/>
              <xs:enumeration value="y2"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute default="head" name="arrowhead">
      <xs:annotation>
        <xs:documentation>
          For vector plots, controls where in the vector the arrow head(s) appear.
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="nohead"/>
              <xs:enumeration value="head"/>
              <xs:enumeration value="heads"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute default="filled" name="arrowstyle">
      <xs:annotation>
        <xs:documentation>
          For vector plots, controls the fill style of the arrow.
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="filled"/>
              <xs:enumeration value="empty"/>
              <xs:enumeration value="nofilled"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute default="0.02" name="arrowlength" type="real-or-perl">
      <xs:annotation>
        <xs:documentation>
          For vector plots, determines the distance between the vector line end and the tip of the arrow.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="10.0" name="arrowangle" type="real-or-perl">
      <xs:annotation>
        <xs:documentation>
          For vector plots, determines the angle the arrow branches make with the vector line.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="90.0" name="arrowbackangle" type="real-or-perl">
      <xs:annotation>
        <xs:documentation>
          For vector plots, determines the angle the arrow lines that return to the main line from the branches make with the arrow branches.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType mixed="true" name="lonplot--data">
    <xs:annotation>
      <xs:documentation>
        Curve data
        
        The data must be either a perl array, @X, or a comma separated list, such as “0.5,0.9,1.5,2.4” (without quotes). ’NaN’ is a valid value. Note the the ”Y” values are entered in a separate array.
      </xs:documentation>
    </xs:annotation>
  </xs:complexType>
  <xs:complexType mixed="true" name="lonplot--function">
    <xs:annotation>
      <xs:documentation>
        Used to specify the curve to be plotted as a formula, instead of numerical data.
        The function must be a mathematical expression. Use the independent variable “x” for cartesian plots and “t” for polar plots. Implicit multiplication is not accepted by Gnuplot.
      </xs:documentation>
    </xs:annotation>
  </xs:complexType>
  <xs:complexType name="lonplot--key">
    <xs:annotation>
      <xs:documentation>
        Causes a key to be drawn on the plot when it is generated. The key will contain an entry for each curve which has a name.
        The key is the color of the foreground of the plot, specified in the gnuplot tag.
      </xs:documentation>
    </xs:annotation>
    <xs:attribute name="title" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          Title of key
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="off" name="box" type="onoff-or-perl">
      <xs:annotation>
        <xs:documentation>
          Draw a box around the key?
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="top right" name="pos">
      <xs:annotation>
        <xs:documentation>
          Position of the key on the plot
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="top left"/>
              <xs:enumeration value="top right"/>
              <xs:enumeration value="bottom left"/>
              <xs:enumeration value="bottom right"/>
              <xs:enumeration value="outside"/>
              <xs:enumeration value="below"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType mixed="true" name="lonplot--label">
    <xs:annotation>
      <xs:documentation>Plot Label</xs:documentation>
    </xs:annotation>
    <xs:attribute default="0" name="xpos" type="real-or-perl">
      <xs:annotation>
        <xs:documentation>
          X position of label (graph coordinates)
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="0" name="ypos" type="real-or-perl">
      <xs:annotation>
        <xs:documentation>
          Y position of label (graph coordinates)
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="left" name="justify">
      <xs:annotation>
        <xs:documentation>
          justification of the label text on the plot
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="left"/>
              <xs:enumeration value="right"/>
              <xs:enumeration value="center"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute default="0" name="rotate" type="real-or-perl">
      <xs:annotation>
        <xs:documentation>
          Rotation of label (degrees)
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType abstract="true" name="lonplot--tics">
    <xs:annotation>
      <xs:documentation>Plot tics</xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded" minOccurs="0">
      <xs:element name="tic">
        <xs:annotation>
          <xs:documentation>
            The <tic> tag allows users to specify exact Tic positions and labels for each axis.
            In this version we only support level 0 tics (major tic).
            Each tic has associated with it a position and a label $current_tics is a reference to the current tick description hash.
            We add elements to an array in that has: ticspecs whose elements are 'pos' - the tick position and 'label' - the tic label.
          </xs:documentation>
        </xs:annotation>
        <xs:complexType mixed="true">
          <xs:attribute name="location" type="real-or-perl" use="required"/>
        </xs:complexType>
      </xs:element>
    </xs:choice>
    <xs:attribute default="border" name="location">
      <xs:annotation>
        <xs:documentation>
          Location of major tic marks
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="border"/>
              <xs:enumeration value="axis"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute default="on" name="mirror" type="onoff-or-perl">
      <xs:annotation>
        <xs:documentation>
          Mirror tics on opposite axis?
          If the location of tic marks is set to “border” this parameter determines if they are shown on both the top and bottom or right and left sides of the graph. The “mirror” tic marks are unlabelled.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="-10.0" name="start" type="real-or-perl">
      <xs:annotation>
        <xs:documentation>
          The point in graph coordinates which to start making major tics. This may be less than or greater than the lower limit for the axis.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="1.0" name="increment" type="real-or-perl">
      <xs:annotation>
        <xs:documentation>
          The span, in graph coordinates, between each major tic mark.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="10.0" name="end" type="real-or-perl">
      <xs:annotation>
        <xs:documentation>
          Stop major tics at.
          This may be less than or greater than the upper limit for the axis.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="0" name="minorfreq" type="int-or-perl">
      <xs:annotation>
        <xs:documentation>
          The number of subdivisions to make of the span between major tic marks. Using a value of “10” leads to 9 minor tic marks.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="off" name="rotate" type="onoff-or-perl">
      <xs:annotation>
        <xs:documentation>
          For output devices that support it, this rotates the tic label by 90 degrees. This is most useful with large lables defined by the tic tag described below.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType name="lonplot--xtics">
    <xs:annotation>
      <xs:documentation>Plot xtics</xs:documentation>
    </xs:annotation>
    <xs:complexContent>
      <xs:extension base="lonplot--tics"/>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="lonplot--ytics">
    <xs:annotation>
      <xs:documentation>Plot ytics</xs:documentation>
    </xs:annotation>
    <xs:complexContent>
      <xs:extension base="lonplot--tics"/>
    </xs:complexContent>
  </xs:complexType>
  <xs:simpleType name="lonplot--xlabel">
    <xs:annotation>
      <xs:documentation>Plot x-label</xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string"/>
  </xs:simpleType>
  <xs:simpleType name="lonplot--ylabel">
    <xs:annotation>
      <xs:documentation>Plot y-label</xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string"/>
  </xs:simpleType>
  <xs:complexType name="lonplot--block">
    <xs:annotation>
      <xs:documentation>
        Conditional Block
        
        This has a required argument condition that is evaluated. If the condition is true, everything inside the tag is evaluated; otherwise, everything inside the block tag is skipped.
        
        When found inside the gnuplot element, a block can only have gnuplot children inside, with no text.
      </xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded" minOccurs="0">
      <xs:group ref="gnuplot-children"/>
    </xs:choice>
    <xs:attribute name="condition" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          Test Condition
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>
  
  <xs:annotation>
    <xs:documentation>
      Task
    </xs:documentation>
  </xs:annotation>
  <xs:element name="Task">
    <xs:annotation>
      <xs:documentation>Root for .task (bridge task) documents</xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="IntroParagraph"/>
        <xs:element ref="Setup"/>
        <xs:element ref="Question"/>
        <xs:element ref="Criteria"/>
        <xs:element ref="ClosingParagraph"/>
      </xs:choice>
      <xs:attribute name="OptionalRequired" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            Required number of passed optional elements to pass the Task
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="IntroParagraph">
    <xs:annotation>
      <xs:documentation>Introductory Information</xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
        <xs:element ref="parserlib"/>
        <xs:element ref="scriptlib"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="ClosingParagraph">
    <xs:annotation>
      <xs:documentation>Closing Information</xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
        <xs:element ref="parserlib"/>
        <xs:element ref="scriptlib"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="Question">
    <xs:annotation>
      <xs:documentation>Question</xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="Instance"/>
        <xs:element ref="QuestionText"/>
        <xs:element ref="Question"/>
        <xs:element ref="Criteria"/>
      </xs:choice>
      <xs:attribute name="id" type="xs:string"/>
      <xs:attribute default="Y" name="Mandatory">
        <xs:annotation>
          <xs:documentation>
            Passing is Mandatory
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="Y"/>
                <xs:enumeration value="N"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="OptionalRequired" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            Required number of passed optional elements to pass
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="QuestionText">
    <xs:annotation>
      <xs:documentation>Question Information</xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
        <xs:element ref="parserlib"/>
        <xs:element ref="scriptlib"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="Setup">
    <xs:annotation>
      <xs:documentation>Setup....</xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="Instance"/>
      </xs:choice>
      <xs:attribute name="id" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="Instance">
    <xs:annotation>
      <xs:documentation>Specific Question Instance</xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="InstanceText"/>
        <xs:element ref="Criteria"/>
      </xs:choice>
      <xs:attribute name="id" type="xs:string"/>
      <xs:attribute default="no" name="Disabled" type="yesno-or-perl">
        <xs:annotation>
          <xs:documentation>
            Instance is Disabled
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="OptionalRequired" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            Required number of passed optional elements to pass the Instance
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="InstanceText">
    <xs:annotation>
      <xs:documentation>Information for the Instance</xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
        <xs:element ref="parserlib"/>
        <xs:element ref="scriptlib"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="Criteria">
    <xs:annotation>
      <xs:documentation>Question Criteria</xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="GraderNote"/>
        <xs:element ref="CriteriaText"/>
      </xs:choice>
      <xs:attribute name="id" type="xs:string"/>
      <xs:attribute default="Y" name="Mandatory">
        <xs:annotation>
          <xs:documentation>
            Passing is Mandatory
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="Y"/>
                <xs:enumeration value="N"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="CriteriaText">
    <xs:annotation>
      <xs:documentation>Criteria Information</xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
        <xs:element ref="parserlib"/>
        <xs:element ref="scriptlib"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="GraderNote">
    <xs:annotation>
      <xs:documentation>Text to display to Grader</xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
        <xs:element ref="parserlib"/>
        <xs:element ref="scriptlib"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  
  <xs:annotation>
    <xs:documentation>
      Problem block elements that cannot be used anywhere text is used.
    </xs:documentation>
  </xs:annotation>
  <xs:element name="part">
    <xs:annotation>
      <xs:documentation>
        Problem Part
        
        This must be below problem if it is going to be used. It does many of the same tasks as problem, but allows multiple separate problems to exist in a single file.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-with-responses"/>
        <xs:group ref="inserts"/>
        <xs:element ref="parameter"/>
        <xs:element ref="parserlib"/>
        <xs:element ref="scriptlib"/>
      </xs:choice>
      <xs:attribute name="id" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Part ID
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="display" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Displayed Part Description
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="allow">
    <xs:annotation>
      <xs:documentation>File Dependencies</xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="src" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Path to the file
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="parserlib" type="xs:anyURI">
    <xs:annotation>
      <xs:documentation>
        Import Tag Definitions
        
        The enclosed filename contains definitions for new tags.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="scriptlib" type="xs:anyURI">
    <xs:annotation>
      <xs:documentation>
        Import Script Library
        
        The enclosed filename contains Perl code to run in the safe space.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="meta">
    <xs:annotation>
      <xs:documentation>
        Custom Metadata for LON-CAPA (as opposed to the HTML meta which should be inside <head>).
        
        Recognized names:
        abstract, author, authorspace, avetries, avetries_list, clear, comefrom, comefrom_list, copyright, correct, count, course, course_list, courserestricted, creationdate, dependencies, depth, difficulty, difficulty_list, disc, disc_list, domain, end, field, firstname, generation, goto, goto_list, groupname, helpful, highestgradelevel, hostname, id, keynum, keywords, language, lastname, lastrevisiondate, lowestgradelevel, middlename, mime, modifyinguser, notes, owner, permanentemail, scope, sequsage, sequsage_list, standards, start, stdno, stdno_list, subject, technical, title, url, username, value, version.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="name" type="xs:string"/>
      <xs:attribute name="content" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="parameter">
    <xs:annotation>
      <xs:documentation>
        Parameter for a part
        
        parameter is exactly the same as responseparam, but should appear outside of a response tag.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="id" type="xs:string"/>
      <xs:attribute name="name" type="xs:string"/>
      <xs:attribute name="type" type="xs:string" use="required"/>
      <xs:attribute name="description" type="xs:string"/>
      <xs:attribute name="default" type="xs:string"/>
      <xs:attribute name="display" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Title displayed on the parameter setting screen.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="displaytitle">
    <xs:annotation>
      <xs:documentation>
        This will insert the title of the problem from the metadata of the problem. Only the first displaytitle in a problem will show the title; this allows clean usage of displaytitle in LON-CAPA style files.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="style" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="displayduedate">
    <xs:annotation>
      <xs:documentation>
        This will insert the current due date if one is set in the document.
        It is generated to be inside a table of 1x1 elements.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="style" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            style=“plain” Makes the due date appear without any boxing. If the parameter value is other than “plain”, or if the style parameter is omitted, the due date will be displayed within a box.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="format" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Allows you to control the format of the due date. This is an arbitrary string that can contain any of the following formatting items:
            
            %a Replaced by the abbreviated weekday name according to the current locale.
            %A Replaced by the full weekday name according to the current locale.
            %b The abbreviated month name according to the current locale.
            %B The full month name according to the current locale.
            %c The preferred date and time representation for the current locale (the default format string is just this).
            %C The century number as a two digit integer
            %d The day of the month as a decimal number. Leading zeroes are shown for single digit day numbers.
            %D Equivalent to %m/%d/%y
            %e Like %d but a leadnig zero is replaced by a space.
            %F Equivalent to %Y-%m-%d
            %G The four digit year number.
            %g The two digit year numbger.
            %H The hour as a two digit number in the range 00 thorugh 23.
            %I The hour as a two digit number in the range 00 through 12.
            %j The day your the year in the range 001 through 366.
            %k The hour (24 hour clock), single digits are preceded by a blank.
            %l Like %k but using a 12 hour clock.
            %m The month as a two digit decimal number in the range 01 through 12.
            %M The minute as a two digit decimal number in the range 00 through 59.
            %n A newline character.
            %p AM or PM depending on the time value.
            %P am or pm.
            %r The time in am or pm notation.
            %R Time in 24 hour notatinon (%H:%M). See also %T below.
            %s Number of seconds since midnight of January 1, 1970.
            %S The second as a decimal number int the range 00 through 59.
            %t A horizontal tab character.
            %T The time in 24 hour notation (%H:%M:%S).
            %u Day of the week as a decimal number with Monday as 1.
            %U The week number of the current year in the range 00 through 53. Week 1 is the week containing the first Sunday of the year.
            %V Same as %U but week 1 is the first week with at least 4 days, with Monday being the first day of a week.
            %w Day of the week as a decimal integer in the range 0 through 7, Sunday is 0.
            %W Week number of the current year in the range 00 through 53, where the first Monday of the year is the first day of week 01.
            %x The preferred date notation in the current locale without the time.
            %X The preferred time notation in the current locale without the date.
            %y The year as a decimal number without the century (range 00 through 99).
            %Y The year as a decimal number including the century.
            %% A % character.
            %+ Date and time in the form returned by the Unix date command. 
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="preduedate">
    <xs:annotation>
      <xs:documentation>
        Before Due Date Block
        
        Everything inside is skipped if the problem is after the due date.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-with-responses"/>
        <xs:element ref="displayduedate"/>
        <xs:element ref="displaytitle"/>
        <xs:element ref="parserlib"/>
        <xs:element ref="scriptlib"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="postanswerdate">
    <xs:annotation>
      <xs:documentation>
        After Answer Date Block
        
        Everything inside is skipped if the problem is before the answer date.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
        <xs:element ref="displayduedate"/>
        <xs:element ref="displaytitle"/>
        <xs:element ref="parserlib"/>
        <xs:element ref="scriptlib"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="solved">
    <xs:annotation>
      <xs:documentation>
        Block For After Solved
        
        Everything inside is skipped if the problem part is “not solved”.
        
        Should not be used outside of parts in a problem using parts.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
        <xs:group ref="inserts"/>
        <xs:element ref="parserlib"/>
        <xs:element ref="scriptlib"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="notsolved">
    <xs:annotation>
      <xs:documentation>
        Block For When Not Solved
        
        Everything inside is skipped if the problem part is “solved”.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-with-responses"/>
        <xs:group ref="inserts"/>
        <xs:element ref="parserlib"/>
        <xs:element ref="scriptlib"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  
  <xs:annotation>
    <xs:documentation>
      Non-HTML block elements mixed with text
    </xs:documentation>
  </xs:annotation>
  <xs:element name="import">
    <xs:annotation>
      <xs:documentation>
        Import a File
        
        This causes the parse to read in the file named in the body of the tag and parse it as if the entire text of the file had existed at the location of the tag.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:simpleContent>
        <xs:extension base="xs:anyURI">
          <xs:attribute name="id" type="xs:string"/>
          <xs:attribute name="importmode">
            <xs:annotation>
              <xs:documentation>
                Import as
              </xs:documentation>
            </xs:annotation>
            <xs:simpleType>
              <xs:union memberTypes="perl">
                <xs:simpleType>
                  <xs:restriction base="xs:string">
                    <xs:enumeration value="problem"/>
                    <xs:enumeration value="part"/>
                  </xs:restriction>
                </xs:simpleType>
              </xs:union>
            </xs:simpleType>
          </xs:attribute>
        </xs:extension>
      </xs:simpleContent>
    </xs:complexType>
  </xs:element>
  <xs:complexType mixed="true" name="block-base">
    <xs:annotation>
      <xs:documentation>
        Conditional Block
        
        This has a required argument condition that is evaluated. If the condition is true, everything inside the tag is evaluated; otherwise, everything inside the block tag is skipped.
      </xs:documentation>
    </xs:annotation>
    <xs:attribute name="condition" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          Test Condition
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType mixed="true" name="block-with-parts">
    <xs:complexContent mixed="true">
      <xs:extension base="block-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-with-parts"/>
          <xs:group ref="inserts"/>
          <xs:element ref="parserlib"/>
          <xs:element ref="scriptlib"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType mixed="true" name="block-with-responses">
    <xs:complexContent mixed="true">
      <xs:extension base="block-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-with-responses"/>
          <xs:group ref="inserts"/>
          <xs:element ref="parserlib"/>
          <xs:element ref="scriptlib"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType mixed="true" name="block-with-text">
    <xs:complexContent mixed="true">
      <xs:extension base="block-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-only"/>
          <xs:group ref="inserts"/>
          <xs:element ref="parserlib"/>
          <xs:element ref="scriptlib"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:annotation>
    <xs:documentation>
      see also: lonplot--block
    </xs:documentation>
  </xs:annotation>
  <xs:element name="while">
    <xs:annotation>
      <xs:documentation>
        While Loop Block
        
        This implements a while loop. The required attribute condition is a Perl scriptlet that when evaluated results in a true or false value. If true, the entirety of the text between the whiles is parsed. The condition is tested again, etc. If false, it goes to the next tag.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
        <xs:group ref="inserts"/>
        <xs:element ref="parserlib"/>
        <xs:element ref="scriptlib"/>
      </xs:choice>
      <xs:attribute name="condition" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Test Condition
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="tex" type="xs:string">
    <xs:annotation>
      <xs:documentation>
        Print Only Block (using only LaTeX)
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="print">
    <xs:annotation>
      <xs:documentation>
        Print Only Block (using HTML)
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="web">
    <xs:annotation>
      <xs:documentation>
        Web Only Block
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="standalone">
    <xs:annotation>
      <xs:documentation>
        Everything in between the start and end tag is shown only on the web and only if the resource is not part of a course.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:complexType mixed="true" name="problemtype-base">
    <xs:annotation>
      <xs:documentation>
        Problem Type Block
        
        Allows you to show or hide output based on what the problem-type parameter is set to in the course.
        Will only show the output text when the problem is set to the type of exam or survey in the course.
      </xs:documentation>
    </xs:annotation>
    <xs:attribute default="show" name="mode">
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="show"/>
              <xs:enumeration value="hide"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute default="exam" name="for">
      <xs:annotation>
        <xs:documentation>
          When used as type(s)
          
          Comma-separated list of values among:
          exam, survey, surveycred, anonsurvey, anonsurveycred, problem, practice, randomizetry
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:pattern value="(exam|survey|surveycred|anonsurvey|anonsurveycred|problem|practice|randomizetry)(\s*,\s*(exam|survey|surveycred|anonsurvey|anonsurveycred|problem|practice|randomizetry))*"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType mixed="true" name="problemtype-with-parts">
    <xs:complexContent mixed="true">
      <xs:extension base="problemtype-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-with-parts"/>
          <xs:group ref="inserts"/>
          <xs:element ref="parserlib"/>
          <xs:element ref="scriptlib"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType mixed="true" name="problemtype-with-responses">
    <xs:complexContent mixed="true">
      <xs:extension base="problemtype-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-with-responses"/>
          <xs:group ref="inserts"/>
          <xs:element ref="parserlib"/>
          <xs:element ref="scriptlib"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType mixed="true" name="problemtype-with-text">
    <xs:complexContent mixed="true">
      <xs:extension base="problemtype-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-only"/>
          <xs:group ref="inserts"/>
          <xs:element ref="parserlib"/>
          <xs:element ref="scriptlib"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="randomlist-base">
    <xs:annotation>
      <xs:documentation>
        Randomly Parsed Block
        
        The enclosed tags are parsed in a stable random order. The optional attribute show=“N” restricts the number of tags inside that are actually parsed to no more than N. N can equal the total tags inside. The randomlist tag can be used to randomize problem parts by wrapping the <part> tags with a randomlist tag. Note that when randomlist wraps <part> tags, that all students will work all parts only if show=“N” where N is the total number of parts wrapped. When N is less than the total number of parts wrapped, there will be gaps in the assessment chart, and also in the table of submissions for each student, corresponding to those parts which are never available to that particular student.
      </xs:documentation>
    </xs:annotation>
    <xs:attribute name="show" type="int-or-perl">
      <xs:annotation>
        <xs:documentation>
          Maximum Tags to Show
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType name="randomlist-with-parts">
    <xs:complexContent>
      <xs:extension base="randomlist-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:element ref="part"/>
          <xs:group ref="responses"/>
          <xs:element ref="img"/>
          <xs:element ref="postanswerdate"/>
          <xs:element ref="preduedate"/>
          <xs:element name="block" type="block-with-parts"/>
          <xs:element ref="while"/>
          <xs:element name="problemtype" type="problemtype-with-parts"/>
          <xs:element ref="window"/>
          <xs:element ref="display"/>
          <xs:element ref="gnuplot"/>
          <xs:element ref="organicstructure"/>
          <xs:element ref="instructorcomment"/>
          <xs:element ref="drawimage"/>
          <xs:element ref="import"/>
          
          <xs:element name="section" type="section-with-parts"/>
          <xs:element name="ul" type="ul-with-parts"/>
          <xs:element name="ol" type="ol-with-parts"/>
          <xs:element name="table" type="table-with-parts"/>
          <xs:element name="dl" type="dl-with-parts"/>
          <xs:element ref="object"/>
          <xs:element ref="applet"/>
          <xs:element ref="embed"/>
          <xs:element ref="video"/>
          <xs:element ref="audio"/>
          <xs:element ref="canvas"/>
          <xs:element ref="form"/>
          <xs:element ref="iframe"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="randomlist-with-responses">
    <xs:complexContent>
      <xs:extension base="randomlist-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="responses"/>
          <xs:element ref="img"/>
          <xs:element ref="postanswerdate"/>
          <xs:element ref="preduedate"/>
          <xs:element name="block" type="block-with-responses"/>
          <xs:element ref="while"/>
          <xs:element name="problemtype" type="problemtype-with-responses"/>
          <xs:element ref="window"/>
          <xs:element ref="display"/>
          <xs:element ref="gnuplot"/>
          <xs:element ref="organicstructure"/>
          <xs:element ref="instructorcomment"/>
          <xs:element ref="drawimage"/>
          <xs:element ref="import"/>
          
          <xs:element name="section" type="section-with-responses"/>
          <xs:element name="ul" type="ul-with-responses"/>
          <xs:element name="ol" type="ol-with-responses"/>
          <xs:element name="table" type="table-with-responses"/>
          <xs:element name="dl" type="dl-with-responses"/>
          <xs:element ref="object"/>
          <xs:element ref="applet"/>
          <xs:element ref="embed"/>
          <xs:element ref="video"/>
          <xs:element ref="audio"/>
          <xs:element ref="canvas"/>
          <xs:element ref="form"/>
          <xs:element ref="iframe"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="randomlist-with-text">
    <xs:complexContent>
      <xs:extension base="randomlist-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:element ref="img"/>
          <xs:element ref="postanswerdate"/>
          <xs:element ref="preduedate"/>
          <xs:element name="block" type="block-with-text"/>
          <xs:element ref="while"/>
          <xs:element name="problemtype" type="problemtype-with-text"/>
          <xs:element ref="window"/>
          <xs:element ref="display"/>
          <xs:element ref="gnuplot"/>
          <xs:element ref="organicstructure"/>
          <xs:element ref="instructorcomment"/>
          <xs:element ref="drawimage"/>
          <xs:element ref="import"/>
          
          <xs:element name="section" type="section-with-text"/>
          <xs:element name="ul" type="ul-with-text"/>
          <xs:element name="ol" type="ol-with-text"/>
          <xs:element name="table" type="table-with-text"/>
          <xs:element name="dl" type="dl-with-text"/>
          <xs:element ref="object"/>
          <xs:element ref="applet"/>
          <xs:element ref="embed"/>
          <xs:element ref="video"/>
          <xs:element ref="audio"/>
          <xs:element ref="canvas"/>
          <xs:element ref="form"/>
          <xs:element ref="iframe"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:element name="script">
    <xs:annotation>
      <xs:documentation>
        Perl Script Block
        
        If the attribute type is set to “loncapa/perl” the enclosed data is a Perl script which is evaluated inside the Perl safe space. The return value of the script is ignored.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:attribute default="text/javascript" name="type" type="xs:string"/>
      <xs:attribute fixed="preserve" ref="xml:space"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="languageblock">
    <xs:annotation>
      <xs:documentation>
        This declares the intent to provide content that can be rendered in the set of languages in the include specification but not in the exclude specification. If a currently preferred language is in the include list the content in the <languageblock>...</languageblock> is rendered If the currently preferred language is in the exclude list, the content in the <languageblock>..</languageblock is not rendered.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attribute name="include" type="xs:string"/>
      <xs:attribute name="exclude" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="translated">
    <xs:annotation>
      <xs:documentation>
        <translated> starts a block of a resource that has multiple translations.
        See the <lang> tag as well.
        When </translated> is encountered if there is a translation for the currently preferred language, that is rendered inthe web/tex/webgrade targets. Otherwise, the default text is rendered.
        Note that <lang> is only registered for the duration of the <translated>...</translated> block.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
        <xs:element ref="lang"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="lang">
    <xs:annotation>
      <xs:documentation>
        Specifies that the block contained within it is a translation for a specific language specified by the 'which' attribute. The 'other' attribute can be used by itself or in conjunction with which to specify this tag _may_ be used as a translation for some list of languages. e.g.:
        <lang which='senisoUS' other='senisoCA,senisoAU,seniso'>
        specifying that the block provides a translation for US (primary) Canadian, Australian and UK English.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attribute name="which" type="xs:string"/>
      <xs:attribute name="other" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="window">
    <xs:annotation>
      <xs:documentation>
        Text In Separate Window
        
        This creates a link that when clicked shows the intervening information in a pop-up window. By default the window will be 500 pixels wide and 200 pixels tall, and the link text will be a superscript * (so as to look like a footnote). These can be changed using the attributes.
        When printing, the included text will get turned into a real footnote.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
        <xs:group ref="inserts"/>
      </xs:choice>
      <xs:attribute name="linktext" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Text of Link
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="200" name="height" type="int-or-perl"/>
      <xs:attribute default="500" name="width" type="int-or-perl"/>
      <xs:attribute name="printtext" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Printed text (optional)
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="windowlink">
    <xs:annotation>
      <xs:documentation>
        This creates a link to a resource that comes up in a pop-up window.
        The link will be the intervening information between the start and the end tag.
        By default the window will be 500 pixels wide and 200 pixels tall.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attribute name="href" type="xs:anyURI"/>
      <xs:attribute name="height" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            starting height of the popup window
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="width" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            starting width of the popup window
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="togglebox">
    <xs:annotation>
      <xs:documentation>
        This creates a toggling box that can be clicked open and close.
        When printing, the included text will be rendered in a visible box.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attribute name="heading" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            heading text of the box, by default no heading
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="headerbg" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            background color of the header, by default white
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="showtext" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            the text that appears to make the box visible, by default the translation of ’show’
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="hidetext" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            the text that appears to hide the box again, by default the translation of ’hide’
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="instructorcomment">
    <xs:annotation>
      <xs:documentation>
        Comment that is hidden if form.instructor_comments='hide'.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="comment">
    <xs:annotation>
      <xs:documentation>
        Allows one to comment out sections of code in a balanced manner, or to provide a comment description of how a problem works.
        The content is ignored.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="part"/>
        <xs:group ref="text-with-responses"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="organicstructure">
    <xs:annotation>
      <xs:documentation>Organic Structure</xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="width" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            Width (pixels)
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="texwidth" type="decimal-or-perl">
        <xs:annotation>
          <xs:documentation>
            TeXwidth (mm)
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="molecule" type="xs:string" use="required">
        <xs:annotation>
          <xs:documentation>
            JME string
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="reaction" name="options">
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="reaction"/>
                <xs:enumeration value="border"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="drawimage">
    <xs:annotation>
      <xs:documentation>
        Draws an image with the specified objects using pixel coordinates (text, line, rectangle, arc, fill, polygon, image).
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element name="text">
          <xs:complexType mixed="true">
            <xs:attribute name="x" type="int-or-perl" use="required"/>
            <xs:attribute name="y" type="int-or-perl" use="required"/>
            <xs:attribute name="font" type="xs:string"/>
            <xs:attribute name="color" type="xs:string"/>
            <xs:attribute name="direction" type="xs:string"/>
          </xs:complexType>
        </xs:element>
        <xs:element name="line">
          <xs:complexType>
            <xs:attribute name="x1" type="int-or-perl" use="required"/>
            <xs:attribute name="y1" type="int-or-perl" use="required"/>
            <xs:attribute name="x2" type="int-or-perl" use="required"/>
            <xs:attribute name="y2" type="int-or-perl" use="required"/>
            <xs:attribute name="color" type="xs:string"/>
            <xs:attribute name="thickness" type="int-or-perl"/>
          </xs:complexType>
        </xs:element>
        <xs:element name="rectangle">
          <xs:complexType>
            <xs:attribute name="x1" type="int-or-perl" use="required"/>
            <xs:attribute name="y1" type="int-or-perl" use="required"/>
            <xs:attribute name="x2" type="int-or-perl" use="required"/>
            <xs:attribute name="y2" type="int-or-perl" use="required"/>
            <xs:attribute name="color" type="xs:string"/>
            <xs:attribute name="thickness" type="int-or-perl"/>
            <xs:attribute name="filled" type="xs:string"/>
          </xs:complexType>
        </xs:element>
        <xs:element name="arc">
          <xs:complexType>
            <xs:attribute name="x" type="int-or-perl" use="required"/>
            <xs:attribute name="y" type="int-or-perl" use="required"/>
            <xs:attribute name="width" type="int-or-perl" use="required"/>
            <xs:attribute name="height" type="int-or-perl" use="required"/>
            <xs:attribute name="start" type="real-or-perl"/>
            <xs:attribute name="end" type="real-or-perl"/>
            <xs:attribute name="color" type="xs:string"/>
            <xs:attribute name="thickness" type="int-or-perl"/>
            <xs:attribute name="filled" type="xs:string"/>
          </xs:complexType>
        </xs:element>
        <xs:element name="fill">
          <xs:complexType>
            <xs:attribute name="x" type="int-or-perl" use="required"/>
            <xs:attribute name="y" type="int-or-perl" use="required"/>
            <xs:attribute name="color" type="xs:string"/>
          </xs:complexType>
        </xs:element>
        <xs:element name="polygon">
          <xs:complexType>
            <xs:choice maxOccurs="unbounded">
              <xs:element name="point">
                <xs:complexType>
                  <xs:attribute name="x" type="int-or-perl" use="required"/>
                  <xs:attribute name="y" type="int-or-perl" use="required"/>
                </xs:complexType>
              </xs:element>
            </xs:choice>
            <xs:attribute name="color" type="xs:string"/>
            <xs:attribute name="filled" type="xs:string"/>
            <xs:attribute name="open" type="xs:string"/>
            <xs:attribute name="thickness" type="int-or-perl"/>
          </xs:complexType>
        </xs:element>
        <xs:element name="image">
          <xs:complexType>
            <xs:simpleContent>
              <xs:extension base="xs:anyURI">
                <xs:attribute name="x" type="int-or-perl" use="required"/>
                <xs:attribute name="y" type="int-or-perl" use="required"/>
                <xs:attribute name="clipx" type="int-or-perl"/>
                <xs:attribute name="clipy" type="int-or-perl"/>
                <xs:attribute name="clipwidth" type="int-or-perl"/>
                <xs:attribute name="clipheight" type="int-or-perl"/>
                <xs:attribute name="scaledwidth" type="int-or-perl"/>
                <xs:attribute name="scaledheight" type="int-or-perl"/>
                <xs:attribute name="transparent" type="xs:string"/>
              </xs:extension>
            </xs:simpleContent>
          </xs:complexType>
        </xs:element>
      </xs:choice>
      <xs:attribute default="300" name="width" type="int-or-perl"/>
      <xs:attribute default="300" name="height" type="int-or-perl"/>
      <xs:attribute name="bgcolor" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  
  <xs:annotation>
    <xs:documentation>
      Non-HTML inline elements mixed with text
    </xs:documentation>
  </xs:annotation>
  <xs:element name="display" type="xs:string">
    <xs:annotation>
      <xs:documentation>
        Display Script Result Block
        
        The intervening Perl script is evaluated in the safe space and the return value of the script replaces the entire tag.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="m">
    <xs:annotation>
      <xs:documentation>
        The inside text is LaTeX, and is converted to HTML (or MathML) on the fly.
        This element is normally used for math, and the text should start and end with either $ or $$.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:attribute name="display" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Option to force the math rendering for this element.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="off" name="eval" type="onoff-or-perl">
        <xs:annotation>
          <xs:documentation>
            Perl variables inside the element will be evaluated if this attribute value is "on".
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="lm">
    <xs:annotation>
      <xs:documentation>
        Inline math with the LON-CAPA syntax (use <m> for LaTeX math).
        Perl variables are evaluated.
        The expression is interpreted with implicit operators (for multiplication or units).
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:simpleContent>
        <xs:extension base="xs:string">
          <xs:attribute default="symbols" name="mode">
            <xs:annotation>
              <xs:documentation>
                In symbols mode, names are interpreted as constants or variables.
                In units mode, names are interpreted as constants or units.
              </xs:documentation>
            </xs:annotation>
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="symbols"/>
                <xs:enumeration value="units"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:attribute>
        </xs:extension>
      </xs:simpleContent>
    </xs:complexType>
  </xs:element>
  <xs:element name="num">
    <xs:annotation>
      <xs:documentation>
        Typesets a number formatted in scientific notation, fixed point, fixed point with commas, fixed point with commas and dollar sign, or in significant digits.
        
        <num format="2E">31454678</num> results in 3.15 x 10^7
        <num format="2f">31454678</num> results in 31454678.00
        <num format="4g">31454678</num> results in 3.145 x 10^7
        <num format="4g">314.54678</num> results in 314.5
        <num format=",2f">31454678</num> results in 31,454,678.00
        <num format="$2f">31454678</num> results in $31,454,678.00
        <num format="2s">31454678</num> results in 31000000
        <num format=",2s">31454678</num> results in 31,000,000
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:attribute name="format" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="algebra">
    <xs:annotation>
      <xs:documentation>
        Typesets algebraic expressions.
        Expressions are displayed using the math expression display mechanism defined in the user’s preferences. The default is tth.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:attribute name="style" type="xs:string"/>
      <xs:attribute name="display" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="chem" type="xs:string">
    <xs:annotation>
      <xs:documentation>
        Typesets chemical equation
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="parse" type="xs:string">
    <xs:annotation>
      <xs:documentation>
        Evaluates the Perl content, then parses it as if it was part of the XML document and displays the result.
        
        Warning: using this element (or the xmlparse function) will reduce the document future interoperability, because dynamically generated XML cannot be automatically converted.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="displayweight">
    <xs:annotation>
      <xs:documentation>
        Displays the number of points awarded for this problem or problem part.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="displaystudentphoto">
    <xs:complexType>
      <xs:attribute name="width" type="int-or-perl"/>
      <xs:attribute name="height" type="int-or-perl"/>
      <xs:attribute name="align">
        <xs:annotation>
          <xs:documentation>
            note: this attribute is not supported in HTML5, css should be used instead !
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="left"/>
                <xs:enumeration value="right"/>
                <xs:enumeration value="middle"/>
                <xs:enumeration value="top"/>
                <xs:enumeration value="bottom"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  
  
  <xs:annotation>
    <xs:documentation>
      HTML
    </xs:documentation>
  </xs:annotation>
  <xs:attributeGroup name="coreattrs">
    <xs:annotation>
      <xs:documentation>
        core attributes common to most HTML elements
      </xs:documentation>
    </xs:annotation>
    <xs:attribute name="id" type="xs:ID">
      <xs:annotation>
        <xs:documentation>
          This attribute defines a unique identifier (ID) which must be unique in the whole document. Its purpose is to identify the element when linking (using a fragment identifier), scripting, or styling (with CSS).
          
          Usage note:
          - This attribute's value is an opaque string: this means that web author must not use it to convey any information. Particular meaning, for example semantic meaning, must not be derived from the string.
          - This attribute's value must not contain white spaces. Browsers treat non-conforming IDs that contains white spaces as if the white space is part of the ID. In contrast to the class attribute, which allows space-separated values, elements can only have one single ID defined through the id attribute. Note that an element may have several IDs, but the others should be set by another means, such as via a script interfacing with the DOM interface of the element.
          - Using characters except ASCII letters and digits, '_', '-' and '.' may cause compatibility problems, as they weren't allowed in HTML 4. Though this restriction has been lifted in HTML 5, an ID should start with a letter for compatibility.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="class" type="xs:NMTOKENS">
      <xs:annotation>
        <xs:documentation>
          This attribute is a space-separated list of the classes of the element. Classes allows CSS and Javascript to select and access specific elements via the class selectors or functions like the DOM method document.getElementsByClassName.
          
          Usage note: Though the specification doesn't put requirements on the name of classes, web developers are encouraged to use names that describe the semantic purpose of the element, rather to the presentation of the element (e.g., attribute to describe an attribute rather than italics, although an element of this class may be presented by italics). Semantic names remain logical even if the presentation of the page changes.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="style" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          This attribute contains CSS styling declarations to be applied to the element. Note that it is recommended for styles to be defined in a separate file or files. This attribute and the <style> element have mainly the purpose of allowing for quick styling, for example for testing purposes.
          
          Usage note: This attribute must not be used to convey semantic information. Even if all styling is removed, a page should remain semantically correct. Typically it shouldn't be used to hide irrelevant information; this should be done using the hidden attribute.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:attributeGroup>
  <xs:attributeGroup name="i18n">
    <xs:annotation>
      <xs:documentation>
        internationalization attributes
        lang language code (backwards compatible)
        xml:lang language code (as per XML 1.0 spec)
        dir direction for weak/neutral text
      </xs:documentation>
    </xs:annotation>
    <xs:attribute name="lang" type="xs:language"/>
    <xs:attribute ref="xml:lang"/>
    <xs:attribute name="dir">
      <xs:simpleType>
        <xs:restriction base="xs:token">
          <xs:enumeration value="ltr"/>
          <xs:enumeration value="rtl"/>
        </xs:restriction>
      </xs:simpleType>
    </xs:attribute>
  </xs:attributeGroup>
  <xs:complexType mixed="true" name="inlineBaseType">
    <xs:choice maxOccurs="unbounded" minOccurs="0">
      <xs:group ref="inlines"/>
    </xs:choice>
  </xs:complexType>
  <xs:group name="heading">
    <xs:choice>
      <xs:element ref="h1"/>
      <xs:element ref="h2"/>
      <xs:element ref="h3"/>
      <xs:element ref="h4"/>
      <xs:element ref="h5"/>
      <xs:element ref="h6"/>
    </xs:choice>
  </xs:group>
  <xs:complexType mixed="true" name="headerContent">
    <xs:choice maxOccurs="unbounded" minOccurs="0">
      <xs:group ref="inlines"/>
    </xs:choice>
    <xs:attributeGroup ref="coreattrs"/>
    <xs:attribute ref="TeXsize"/>
  </xs:complexType>
  
  <xs:element name="html">
    <xs:annotation>
      <xs:documentation>
        The HTML root element (<html>) represents the root of an HTML document.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="head"/>
        <xs:element ref="body"/>
      </xs:sequence>
      <xs:attributeGroup ref="i18n"/>
      <xs:attribute name="id" type="xs:ID"/>
    </xs:complexType>
  </xs:element>
  <xs:group name="head.misc">
    <xs:sequence>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element name="script" type="htmlScript"/>
        <xs:element ref="style"/>
        <xs:element name="meta" type="htmlMeta">
          <xs:annotation>
            <xs:documentation>
              generic metainformation
            </xs:documentation>
          </xs:annotation>
        </xs:element>
        <xs:element ref="link"/>
        <xs:element ref="import"/>
      </xs:choice>
    </xs:sequence>
  </xs:group>
  <xs:element name="head">
    <xs:annotation>
      <xs:documentation>
        The HTML Head Element (<head>) provides general information (metadata) about the document, including its title and links to or definitions of scripts and style sheets.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:sequence>
        <xs:group ref="head.misc"/>
        <xs:choice>
          <xs:sequence>
            <xs:element ref="title"/>
            <xs:group ref="head.misc"/>
            <xs:sequence minOccurs="0">
              <xs:element ref="base"/>
              <xs:group ref="head.misc"/>
            </xs:sequence>
          </xs:sequence>
          <xs:sequence>
            <xs:element ref="base"/>
            <xs:group ref="head.misc"/>
            <xs:element ref="title"/>
            <xs:group ref="head.misc"/>
          </xs:sequence>
        </xs:choice>
      </xs:sequence>
      <xs:attributeGroup ref="i18n"/>
      <xs:attribute name="id" type="xs:ID"/>
      <xs:attribute name="profile" type="xs:anyURI">
        <xs:annotation>
          <xs:documentation>
            The URIs of one or more metadata profiles, separated by white space.
            
            This attribute is obsolete in HTML5.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="title">
    <xs:annotation>
      <xs:documentation>
        The title element is not considered part of the flow of text.
        It should be displayed, for example as the page header or
        window title. Exactly one title is required per document.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:attributeGroup ref="i18n"/>
      <xs:attribute name="id" type="xs:ID"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="base">
    <xs:annotation>
      <xs:documentation>
        Document base URI
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="href" type="xs:anyURI" use="required"/>
      <xs:attribute name="id" type="xs:ID"/>
    </xs:complexType>
  </xs:element>
  <xs:complexType name="htmlMeta">
    <xs:attributeGroup ref="i18n"/>
    <xs:attribute name="id" type="xs:ID"/>
    <xs:attribute name="http-equiv">
      <xs:annotation>
        <xs:documentation>
          This enumerated attribute defines the pragma that can alter servers and user-agents behavior. The value of the pragma is defined using the content and can be one of the following: 
          - content-language (obsolete)
          - content-type (obsolete)
          - default-style
          - refresh
          - set-cookie (obsolete)
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="name">
      <xs:annotation>
        <xs:documentation>
          This attribute defines the name of a document-level metadata. It should not be set if one of the attributes itemprop, http-equiv or charset is also set.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="content" use="required">
      <xs:annotation>
        <xs:documentation>
          This attribute gives the value associated with the http-equiv or name attribute, depending of the context.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="scheme">
      <xs:annotation>
        <xs:documentation>
          This attribute defines the scheme in which the metadata is described. A scheme is a context leading to the correct interpretations of the content value, like a format.
          
          Notes: Do not use this attribute as it is obsolete. There is no replacement for it as there was no real usage for it. Omit it altogether.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>
  <xs:simpleType name="MediaDesc">
    <xs:annotation>
      <xs:documentation>
        Single or comma-separated list of media descriptors
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string">
      <xs:pattern value="[^,]+(,\s*[^,]+)*"/>
    </xs:restriction>
  </xs:simpleType>
  <xs:element name="link">
    <xs:annotation>
      <xs:documentation>
        The HTML Link Element (<link>) specifies relationships between the current document and external resource. Possible uses for this element include defining a relational framework for navigation. This Element is most used to link to style sheets.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="charset" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            This attribute defines the character encoding of the linked resource. The value is a space- and/or comma-delimited list of character sets as defined in RFC 2045. The default value is ISO-8859-1.
            
            Usage note: This attribute is obsolete in HTML5 and must not be used by authors. To achieve its effect, use the Content-Type: HTTP header on the linked resource.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="href" type="xs:anyURI">
        <xs:annotation>
          <xs:documentation>
            This attribute specifies the URL of the linked resource. A URL might be absolute or relative.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="hreflang" type="xs:language">
        <xs:annotation>
          <xs:documentation>
            This attribute indicates the language of the linked resource. It is purely advisory. Allowed values are determined by BCP47 for HTML5 and by RFC1766 for HTML 4. Use this attribute only if the href attribute is present.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="type" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            This attribute is used to define the type of the content linked to. The value of the attribute should be a MIME type such as text/html, text/css, and so on. The common use of this attribute is to define the type of style sheet linked and the most common current value is text/css, which indicates a Cascading Style Sheet format.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="rel" type="xs:NMTOKENS">
        <xs:annotation>
          <xs:documentation>
            This attribute names a relationship of the linked document to the current document. The attribute must be a space-separated list of the link types values. The most common use of this attribute is to specify a link to an external style sheet: the rel attribute is set to stylesheet, and the href attribute is set to the URL of an external style sheet to format the page. WebTV also supports the use of the value next for rel to preload the next page in a document series.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="rev" type="xs:NMTOKENS">
        <xs:annotation>
          <xs:documentation>
            The value of this attribute shows the relationship of the current document to the linked document, as defined by the href attribute. The attribute thus defines the reverse relationship compared to the value of the rel attribute. Link types values for the attribute are similar to the possible values for rel.
            
            Usage note: This attribute is obsolete in HTML5. Do not use it. To achieve its effect, use the rel attribute with the opposite link types values, e.g. made should be replaced by author. Also this attribute doesn't mean revision and must not be used with a version number, which is unfortunately the case on numerous sites.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="media" type="MediaDesc">
        <xs:annotation>
          <xs:documentation>
            This attribute specifies the media which the linked resource applies to. Its value must be a media query. This attribute is mainly useful when linking to external stylesheets by allowing the user agent to pick the best adapted one for the device it runs on.
            
            Usage note:
            
            - In HTML 4, this can only be a simple white-space-separated list of media description literals, i.e., media types and groups, where defined and allowed as values for this attribute, such as print, screen, aural, braille. HTML5 extended this to any kind of media queries, which are a superset of the allowed values of HTML 4.
            
            - Browsers not supporting the CSS3 Media Queries won't necessarily recognize the adequate link; do not forget to set fallback links, the restricted set of media queries defined in HTML 4.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="style">
    <xs:annotation>
      <xs:documentation>
        The HTML <style> element contains style information for a document, or a part of document. The specific style information is contained inside of this element, usually in the CSS.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:attributeGroup ref="i18n"/>
      <xs:attribute name="id" type="xs:ID"/>
      <xs:attribute default="text/css" name="type" type="xs:string"/>
      <xs:attribute name="media" type="MediaDesc"/>
      <xs:attribute name="title" type="xs:string"/>
      <xs:attribute fixed="preserve" ref="xml:space"/>
    </xs:complexType>
  </xs:element>
  <xs:complexType mixed="true" name="htmlScript">
    <xs:annotation>
      <xs:documentation>
        The HTML <script> element is used to embed or reference an executable script within an HTML or XHTML document.
        
        Scripts without async or defer attributes, as well as inline scripts, are fetched and executed immediately, before the browser continues to parse the page.
      </xs:documentation>
    </xs:annotation>
    <xs:attribute name="id" type="xs:ID"/>
    <xs:attribute name="charset" type="xs:string"/>
    <xs:attribute name="src" type="xs:anyURI">
      <xs:annotation>
        <xs:documentation>
          This attribute specifies the URI of an external script; this can be used as an alternative to embedding a script directly within a document. script elements with an src attribute specified should not have a script embedded within its tags.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="text/javascript" name="type" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          This attribute identifies the scripting language of code embedded within a script element or referenced via the element’s src attribute. This is specified as a MIME type; examples of supported MIME types include text/javascript, text/ecmascript, application/javascript, and application/ecmascript.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="language" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          Like the type attribute, this attribute identifies the scripting language in use. Unlike the type attribute, however, this attribute’s possible values were never standardized. The type attribute should be used instead.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="defer">
      <xs:annotation>
        <xs:documentation>
          This Boolean attribute is set to indicate to a browser that the script is meant to be executed after the document has been parsed. Since this feature hasn't yet been implemented by all other major browsers, authors should not assume that the script’s execution will actually be deferred. The defer attribute shouldn't be used on scripts that don't have the src attribute. Since Gecko 1.9.2, the defer attribute is ignored on scripts that don't have the src attribute. However, in Gecko 1.9.1 even inline scripts are deferred if the defer attribute is set.
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:restriction base="xs:token">
          <xs:enumeration value="defer"/>
        </xs:restriction>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute name="async">
      <xs:annotation>
        <xs:documentation>
          HTML5 only.
          Set this Boolean attribute to indicate that the browser should, if possible, execute the script asynchronously. It has no effect on inline scripts (i.e., scripts that don't have the src attribute).
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:restriction base="xs:token">
          <xs:enumeration value="async"/>
        </xs:restriction>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute fixed="preserve" ref="xml:space"/>
  </xs:complexType>
  <xs:element name="noscript">
    <xs:annotation>
      <xs:documentation>
        Alternate content container for non script-based rendering.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="blocks-with-text"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="body">
    <xs:annotation>
      <xs:documentation>
        The HTML <body> element represents the content of an HTML document. There is only one <body> element in a document.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="blocks-with-text"/>
      </xs:choice>
      <xs:attributeGroup ref="coreattrs"/>
      <xs:attributeGroup ref="i18n"/>
      <xs:attribute name="onload" type="xs:string"/>
      <xs:attribute name="onunload" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:complexType mixed="true" name="section-base">
    <xs:annotation>
      <xs:documentation>
        The HTML Section Element (<section>) represents a generic section of a document, i.e., a thematic grouping of content, typically with a heading. Each <section> should be identified, typically by including a heading (h1-h6 element) as a child of the <section> element.
      </xs:documentation>
    </xs:annotation>
    <xs:attributeGroup ref="coreattrs"/>
  </xs:complexType>
  <xs:complexType mixed="true" name="section-with-parts">
    <xs:complexContent mixed="true">
      <xs:extension base="section-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-with-parts"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType mixed="true" name="section-with-responses">
    <xs:complexContent mixed="true">
      <xs:extension base="section-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-with-responses"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType mixed="true" name="section-with-text">
    <xs:complexContent mixed="true">
      <xs:extension base="section-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-only"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:element name="header">
    <xs:annotation>
      <xs:documentation>
        The HTML <header> Element represents a group of introductory or navigational aids. It may contain some heading elements but also other elements like a logo, wrapped section's header, a search form, and so on.
        
        This element should have no <footer> or <header> descendants.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="footer">
    <xs:annotation>
      <xs:documentation>
        The HTML <footer> Element represents a footer for its nearest sectioning content or sectioning root element (i.e, its nearest parent <article>, <aside>, <nav>, <section>, <blockquote>, <body>, <details>, <fieldset>, <figure>, <td>). A footer typically contains information about the author of the section, copyright data or links to related documents.
        
        This element should have no <footer> or <header> descendants.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="aside">
    <xs:annotation>
      <xs:documentation>
        The HTML <aside> element represents a section of the page with content connected tangentially to the rest, which could be considered separate from that content. These sections are often represented as sidebars or inserts. They often contain the definitions on the sidebars, such as definitions from the glossary; there may also be other types of information, such as related advertisements; the biography of the author; web applications; profile information or related links on the blog.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="h1" type="headerContent">
    <xs:annotation>
      <xs:documentation>
        Level 1 title (most important).
        
        A heading element briefly describes the topic of the section it introduces. Heading information may be used by user agents, for example, to construct a table of contents for a document automatically.
        
        Inside HTML5 sections, all the heading elements can be h1 (they don't need to be h1, h2, ...). Web browsers determine the level of the heading based on the depth in the section tree.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="h2" type="headerContent">
    <xs:annotation>
      <xs:documentation>
        Level 2 title
        
        A heading element briefly describes the topic of the section it introduces. Heading information may be used by user agents, for example, to construct a table of contents for a document automatically.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="h3" type="headerContent">
    <xs:annotation>
      <xs:documentation>
        Level 3 title
        
        A heading element briefly describes the topic of the section it introduces. Heading information may be used by user agents, for example, to construct a table of contents for a document automatically.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="h4" type="headerContent">
    <xs:annotation>
      <xs:documentation>
        Level 4 title
        
        A heading element briefly describes the topic of the section it introduces. Heading information may be used by user agents, for example, to construct a table of contents for a document automatically.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="h5" type="headerContent">
    <xs:annotation>
      <xs:documentation>
        Level 5 title
        
        A heading element briefly describes the topic of the section it introduces. Heading information may be used by user agents, for example, to construct a table of contents for a document automatically.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="h6" type="headerContent">
    <xs:annotation>
      <xs:documentation>
        Level 6 title
        
        A heading element briefly describes the topic of the section it introduces. Heading information may be used by user agents, for example, to construct a table of contents for a document automatically.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:complexType mixed="true" name="div-base">
    <xs:annotation>
      <xs:documentation>
        The HTML <div> element (or HTML Document Division Element) is the generic container for flow content, which does not inherently represent anything. It can be used to group elements for styling purposes (using the class or id attributes), or because they share attribute values, such as lang. It should be used only when no other semantic element (such as <article> or <nav>) is appropriate.
      </xs:documentation>
    </xs:annotation>
    <xs:attributeGroup ref="coreattrs"/>
    <xs:attribute name="align">
      <xs:annotation>
        <xs:documentation>
          In HTML5, the align attribute on <div> is obsolete.
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:restriction base="xs:string">
          <xs:enumeration value="center"/>
          <xs:enumeration value="left"/>
          <xs:enumeration value="right"/>
        </xs:restriction>
      </xs:simpleType>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType mixed="true" name="div-with-parts">
    <xs:complexContent mixed="true">
      <xs:extension base="div-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-with-parts"/>
          <xs:group ref="inserts"/>
          <xs:element ref="allow"/>
          <xs:element ref="meta"/>
          <xs:element ref="parameter"/>
          <xs:element ref="parserlib"/>
          <xs:element ref="scriptlib"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType mixed="true" name="div-with-responses">
    <xs:complexContent mixed="true">
      <xs:extension base="div-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-with-responses"/>
          <xs:group ref="inserts"/>
          <xs:element ref="allow"/>
          <xs:element ref="meta"/>
          <xs:element ref="parameter"/>
          <xs:element ref="parserlib"/>
          <xs:element ref="scriptlib"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType mixed="true" name="div-with-text">
    <xs:complexContent mixed="true">
      <xs:extension base="div-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-only"/>
          <xs:group ref="inserts"/>
          <xs:element ref="meta"/>
          <xs:element ref="parameter"/>
          <xs:element ref="parserlib"/>
          <xs:element ref="scriptlib"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType mixed="true" name="p-base">
    <xs:annotation>
      <xs:documentation>
        The HTML <p> element (or HTML Paragraph Element) represents a paragraph of text. Paragraphs are block-level elements.
      </xs:documentation>
    </xs:annotation>
    <xs:attributeGroup ref="coreattrs"/>
  </xs:complexType>
  <xs:complexType mixed="true" name="p-with-responses">
    <xs:complexContent mixed="true">
      <xs:extension base="p-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="inlines"/>
          <xs:group ref="inlineResponses"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType mixed="true" name="p-with-text">
    <xs:complexContent mixed="true">
      <xs:extension base="p-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="inlines"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="ul-base">
    <xs:annotation>
      <xs:documentation>
        Unordered list
      </xs:documentation>
    </xs:annotation>
    <xs:attributeGroup ref="coreattrs"/>
    <xs:attribute name="type" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          Used to set the bullet style for the list. The values defined under HTML3.2 and the transitional version of HTML 4.0/4.01 are:
          - circle,
          - disc,
          - and square.
          
          A fourth bullet type has been defined in the WebTV interface, but not all browsers support it: triangle.
          
          If not present and if no CSS list-style-type property does apply to the element, the user agent decide to use a kind of bullets depending on the nesting level of the list.
          Usage note: Do not use this attribute, as it has been deprecated; use the CSS list-style-type property instead.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType name="ul-with-parts">
    <xs:complexContent>
      <xs:extension base="ul-base">
        <xs:sequence maxOccurs="unbounded">
          <xs:element name="li" type="li-with-parts"/>
        </xs:sequence>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="ul-with-responses">
    <xs:complexContent>
      <xs:extension base="ul-base">
        <xs:sequence maxOccurs="unbounded">
          <xs:element name="li" type="li-with-responses"/>
        </xs:sequence>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="ul-with-text">
    <xs:complexContent>
      <xs:extension base="ul-base">
        <xs:sequence maxOccurs="unbounded">
          <xs:element name="li" type="li-with-text"/>
        </xs:sequence>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="ol-base">
    <xs:annotation>
      <xs:documentation>
        Ordered list
      </xs:documentation>
    </xs:annotation>
    <xs:attributeGroup ref="coreattrs"/>
    <xs:attribute name="type" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          Indicates the numbering type:
          - 'a' indicates lowercase letters,
          - 'A' indicates uppercase letters,
          - 'i' indicates lowercase Roman numerals,
          - 'I' indicates uppercase Roman numerals,
          - and '1' indicates numbers (default).
          
          The type set is used for the entire list unless a different type attribute is used within an enclosed <li> element.
          
          Note: This attribute was deprecated in HTML4, but reintroduced in HTML5. Unless the value of the list number matters (e.g. in legal or technical documents where items are to be referenced by their number/letter), the CSS list-style-type property should be used instead.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType name="ol-with-parts">
    <xs:complexContent>
      <xs:extension base="ol-base">
        <xs:sequence maxOccurs="unbounded">
          <xs:element name="li" type="li-with-parts"/>
        </xs:sequence>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="ol-with-responses">
    <xs:complexContent>
      <xs:extension base="ol-base">
        <xs:sequence maxOccurs="unbounded">
          <xs:element name="li" type="li-with-responses"/>
        </xs:sequence>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="ol-with-text">
    <xs:complexContent>
      <xs:extension base="ol-base">
        <xs:sequence maxOccurs="unbounded">
          <xs:element name="li" type="li-with-text"/>
        </xs:sequence>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType mixed="true" name="li-base">
    <xs:annotation>
      <xs:documentation>
        List item
      </xs:documentation>
    </xs:annotation>
    <xs:attributeGroup ref="coreattrs"/>
    <xs:attribute name="type" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          This character attributes indicates the numbering type:
          
          a: lowercase letters
          A: uppercase letters
          i: lowercase Roman numerals
          I: uppercase Roman numerals
          1: numbers
          
          This type overrides the one used by its parent <ol> element, if any.
          
          Usage note: This attribute has been deprecated: use the CSS list-style-type property instead.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="value" type="xs:int">
      <xs:annotation>
        <xs:documentation>
          This integer attributes indicates the current ordinal value of the item in the list as defined by the <ol> element. The only allowed value for this attribute is a number, even if the list is displayed with Roman numerals or letters. List items that follow this one continue numbering from the value set. The value attribute has no meaning for unordered lists (<ul>) or for menus (<menu>).
          
          Note: This attribute was deprecated in HTML4, but reintroduced in HTML5.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType mixed="true" name="li-with-parts">
    <xs:complexContent mixed="true">
      <xs:extension base="li-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-with-parts"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType mixed="true" name="li-with-responses">
    <xs:complexContent mixed="true">
      <xs:extension base="li-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-with-responses"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType mixed="true" name="li-with-text">
    <xs:complexContent mixed="true">
      <xs:extension base="li-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-only"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="dl-base">
    <xs:annotation>
      <xs:documentation>
        The HTML <dl> Element (or HTML Description List Element) encloses a list of pairs of terms and descriptions. Common uses for this element are to implement a glossary or to display metadata (a list of key-value pairs).
        
        Prior to HTML5, <dl> was known as a Definition List.
      </xs:documentation>
    </xs:annotation>
    <xs:attributeGroup ref="coreattrs"/>
  </xs:complexType>
  <xs:complexType name="dl-with-parts">
    <xs:complexContent>
      <xs:extension base="dl-base">
        <xs:choice maxOccurs="unbounded">
          <xs:element ref="dt"/>
          <xs:element name="dd" type="dd-with-parts"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="dl-with-responses">
    <xs:complexContent>
      <xs:extension base="dl-base">
        <xs:choice maxOccurs="unbounded">
          <xs:element ref="dt"/>
          <xs:element name="dd" type="dd-with-responses"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="dl-with-text">
    <xs:complexContent>
      <xs:extension base="dl-base">
        <xs:choice maxOccurs="unbounded">
          <xs:element ref="dt"/>
          <xs:element name="dd" type="dd-with-text"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:element name="dt" type="inlineBaseType">
    <xs:annotation>
      <xs:documentation>
        The HTML <dt> element (or HTML Definition Term Element) identifies a term in a definition list. This element can occur only as a child element of a <dl>. It is usually followed by a <dd> element; however, multiple <dt> elements in a row indicate several terms that are all defined by the immediate next <dd> element.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:complexType mixed="true" name="dd-base">
    <xs:annotation>
      <xs:documentation>
        The HTML <dd> Element (or HTML Description Element) indicates the description of a term in a description list (<dl>) element. This element can occur only as a child element of a definition list and it must follow a <dt> element.
      </xs:documentation>
    </xs:annotation>
  </xs:complexType>
  <xs:complexType mixed="true" name="dd-with-parts">
    <xs:complexContent mixed="true">
      <xs:extension base="dd-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-with-parts"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType mixed="true" name="dd-with-responses">
    <xs:complexContent mixed="true">
      <xs:extension base="dd-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-with-responses"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType mixed="true" name="dd-with-text">
    <xs:complexContent mixed="true">
      <xs:extension base="dd-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-only"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="table-base">
    <xs:annotation>
      <xs:documentation>
        The HTML Table Element (<table>) represents data in two dimensions or more.
      </xs:documentation>
    </xs:annotation>
    <xs:attributeGroup ref="coreattrs"/>
    <xs:attribute name="border" type="xs:int">
      <xs:annotation>
        <xs:documentation>
          This integer attribute defines, in pixels, the size of the frame surrounding the table. If set to 0, it implies that the frame attribute is set to void.
          Usage note: Do not use this attribute, as it has been deprecated: the <table> element should be styled using CSS. To give a similar effect than the border attribute, the CSS properties border, border-color, border-width and border-style should be used.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="cellpadding" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          This attribute defines the space between the content of a cell and the border, displayed or not, of it. If it is a pixel length, this pixel-sized space will be applied on all four sides; if it is a percentage length, the content will be centered and the total vertical space (top and bottom) will represent this percentage. The same is true for the total horizontal space (left and right).
          Usage note: Do not use this attribute, as it has been deprecated: the <table> element should be styled using CSS. To give a similar effect than the border attribute, use the CSS property border-collapse with the value collapse on the <table> element itself, and the property padding on the <td>.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="cellspacing" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          This attribute defines the size, in percentage or in pixels, of the space between two cells (both horizontally and vertically), between the top of the table and the cells of the first row, the left of the table and the first column, the right of the table and the last column and the bottom of the table and the last row.
          Usage note: Do not use this attribute, as it has been deprecated: the <table> element should be styled using CSS. To give a similar effect than the border attribute, use the CSS property border-collapse with the value collapse on the <table> element itself, and the property margin on the <td> element.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="TeXwidth">
      <xs:annotation>
        <xs:documentation>
          Width of the table in %
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:pattern value="[0-9]+\s*%"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute name="TeXtheme" type="xs:string"/>
    <xs:attribute name="align" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          Deprecated attribute.
          
          This enumerated attribute indicates how the table must be aligned in regard of the containing document. It may have the following values:
          
          - left, meaning that the table is to be displayed to the left of the document;
          - center, meaning that the table is to be displayed centered in the document;
          - right, meaning that the table is to be displayed to the right of the document.
          
          Note: 
          Do not use this attribute, as it has been deprecated: the <table> element should be styled using CSS. To give a similar effect than the align attribute, the CSS properties "text-align" and "vertical-align" should be used.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="rules" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          Deprecated attribute.
          
          This enumerated attribute defines where rules, i.e. lines, should appear in a table. It can have the following values:
          
          - none, which indicates the no rules will be displayed; it is the default value;
          - groups, which will make the rules to be displayed between row groups (defined by the <thead>, <tbody> and <tfoot> elements) and between column groups (defined by the <col> and <colgroup> elements) only;
          - rows, which will make the rules to be displayed between rows;
          - columns, which will make the rules to be displayed between columns;
          - all, which wil make the rules to be displayed between rows and columns.
          
          Note:
          The styling of the rules is browser-dependant and cannot be modified.
          Do not use this attribute, as it has been deprecated: the rules should be defined and styled using CSS. use the CSS property border on the adequate <thead>, <tbody>, <tfoot>, <col> or <colgroup> elements.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType name="table-with-parts">
    <xs:complexContent>
      <xs:extension base="table-base">
        <xs:sequence>
          <xs:element minOccurs="0" ref="caption"/>
          <xs:element minOccurs="0" ref="thead"/>
          <xs:element minOccurs="0" ref="tfoot"/>
          <xs:choice>
            <xs:element maxOccurs="unbounded" name="tbody" type="tbody-with-parts"/>
            <xs:element maxOccurs="unbounded" name="tr" type="tr-with-parts"/>
          </xs:choice>
        </xs:sequence>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="table-with-responses">
    <xs:complexContent>
      <xs:extension base="table-base">
        <xs:sequence>
          <xs:element minOccurs="0" ref="caption"/>
          <xs:element minOccurs="0" ref="thead"/>
          <xs:element minOccurs="0" ref="tfoot"/>
          <xs:choice>
            <xs:element maxOccurs="unbounded" name="tbody" type="tbody-with-responses"/>
            <xs:element maxOccurs="unbounded" name="tr" type="tr-with-responses"/>
          </xs:choice>
        </xs:sequence>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="table-with-text">
    <xs:complexContent>
      <xs:extension base="table-base">
        <xs:sequence>
          <xs:element minOccurs="0" ref="caption"/>
          <xs:element minOccurs="0" ref="thead"/>
          <xs:element minOccurs="0" ref="tfoot"/>
          <xs:choice>
            <xs:element maxOccurs="unbounded" name="tbody" type="tbody-with-text"/>
            <xs:element maxOccurs="unbounded" name="tr" type="tr-with-text"/>
          </xs:choice>
        </xs:sequence>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:element name="caption">
    <xs:annotation>
      <xs:documentation>
        The HTML <caption> Element (or HTML Table Caption Element) represents the title of a table. Though it is always the first descendant of a <table>, its styling, using CSS, may place it elsewhere, relative to the table.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="thead">
    <xs:annotation>
      <xs:documentation>
        The HTML Table Head Element (<thead>) defines a set of rows defining the head of the columns of the table.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" name="tr" type="tr-with-text"/>
      </xs:sequence>
      <xs:attribute name="align" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Deprecated attribute.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="tfoot">
    <xs:annotation>
      <xs:documentation>
        The HTML Table Foot Element (<tfoot>) defines a set of rows summarizing the columns of the table.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" name="tr" type="tr-with-text"/>
      </xs:sequence>
      <xs:attribute name="align" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Deprecated attribute.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:complexType name="tbody-with-parts">
    <xs:sequence>
      <xs:choice>
        <xs:element maxOccurs="unbounded" name="tr" type="tr-with-parts"/>
      </xs:choice>
    </xs:sequence>
    <xs:attribute name="align" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          Deprecated attribute.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType name="tbody-with-responses">
    <xs:sequence>
      <xs:choice>
        <xs:element maxOccurs="unbounded" name="tr" type="tr-with-responses"/>
      </xs:choice>
    </xs:sequence>
    <xs:attribute name="align" type="xs:string"/>
  </xs:complexType>
  <xs:complexType name="tbody-with-text">
    <xs:sequence>
      <xs:choice>
        <xs:element maxOccurs="unbounded" name="tr" type="tr-with-text"/>
      </xs:choice>
    </xs:sequence>
    <xs:attribute name="align" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          Deprecated attribute.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType name="tr-base">
    <xs:annotation>
      <xs:documentation>
        Table row
      </xs:documentation>
    </xs:annotation>
    <xs:attributeGroup ref="coreattrs"/>
    <xs:attribute name="align" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          Deprecated attribute.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType name="tr-with-parts">
    <xs:complexContent>
      <xs:extension base="tr-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:element ref="th"/>
          <xs:element name="td" type="td-with-parts"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="tr-with-responses">
    <xs:complexContent>
      <xs:extension base="tr-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:element ref="th"/>
          <xs:element name="td" type="td-with-responses"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="tr-with-text">
    <xs:complexContent>
      <xs:extension base="tr-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:element ref="th"/>
          <xs:element name="td" type="td-with-text"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:simpleType name="html-align">
    <xs:restriction base="xs:string">
      <xs:enumeration value="left"/>
      <xs:enumeration value="center"/>
      <xs:enumeration value="right"/>
      <xs:enumeration value="justify"/>
    </xs:restriction>
  </xs:simpleType>
  <xs:complexType mixed="true" name="td-base">
    <xs:annotation>
      <xs:documentation>
        Table cell
      </xs:documentation>
    </xs:annotation>
    <xs:attributeGroup ref="coreattrs"/>
    <xs:attribute name="colspan" type="xs:int">
      <xs:annotation>
        <xs:documentation>
          This attribute contains a non-negative integer value that indicates on how many columns does the cell extend. Its default value is 1.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="rowspan" type="xs:int">
      <xs:annotation>
        <xs:documentation>
          This attribute contains a non-negative integer value that indicates on how many rows does the cell extend. Its default value is 1.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="align" type="html-align"/>
    <xs:attribute name="TeXwidth">
      <xs:annotation>
        <xs:documentation>
          Width of the cell in mm or another unit (cm, in, pt, pc)
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:pattern value="\d*(\s+(mm|cm|in|pt|pc))?"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType mixed="true" name="td-with-parts">
    <xs:complexContent mixed="true">
      <xs:extension base="td-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-with-parts"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType mixed="true" name="td-with-responses">
    <xs:complexContent mixed="true">
      <xs:extension base="td-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-with-responses"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType mixed="true" name="td-with-text">
    <xs:complexContent mixed="true">
      <xs:extension base="td-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-only"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:element name="th">
    <xs:annotation>
      <xs:documentation>
        Table header cell
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="coreattrs"/>
      <xs:attribute name="colspan" type="xs:int">
        <xs:annotation>
          <xs:documentation>
            This attribute contains a non-negative integer value that indicates on how many columns does the cell extend. Its default value is 1.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="rowspan" type="xs:int">
        <xs:annotation>
          <xs:documentation>
            This attribute contains a non-negative integer value that indicates on how many rows does the cell extend. Its default value is 1.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="align" type="html-align"/>
      <xs:attribute name="scope">
        <xs:annotation>
          <xs:documentation>
            defines the cells that the header defined in this <th> element relates to
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="row"/>
            <xs:enumeration value="col"/>
            <xs:enumeration value="rowgroup"/>
            <xs:enumeration value="colgroup"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="TeXwidth">
        <xs:annotation>
          <xs:documentation>
            Width of the cell in mm or another unit (cm, in, pt, pc)
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:pattern value="\d*(\s+(mm|cm|in|pt|pc))?"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="span">
    <xs:annotation>
      <xs:documentation>
        Inline style
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="inlines"/>
      </xs:choice>
      <xs:attributeGroup ref="coreattrs"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="a">
    <xs:annotation>
      <xs:documentation>
        The HTML <a> Element (or the HTML Anchor Element) defines a hyperlink, the named target destination for a hyperlink, or both.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="inlines"/>
      </xs:choice>
      <xs:attributeGroup ref="coreattrs"/>
      <xs:attribute name="name" type="xs:string"/>
      <xs:attribute name="href" type="xs:anyURI"/>
      <xs:attribute name="target" type="xs:string"/>
      <xs:attribute name="title" type="xs:string"/>
      <xs:attribute name="uriprint" type="xs:string"/>
      <xs:attribute name="anchorprint" type="xs:string"/>
      <xs:attribute name="rel" type="xs:NMTOKENS"/>
      <xs:attribute name="accesskey">
        <xs:simpleType>
          <xs:restriction base="xs:string">
            <xs:length fixed="true" value="1"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="onclick" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Javascript event handler content attribute for the "click" event.
            
            Warning: event handler content attributes should be avoided. They make the markup bigger and less readable. Concerns of content/structure and behavior are not well-separated, making a bug harder to find. Furthermore, usage of event attributes almost always causes scripts to expose global functions on the Window object, polluting the global namespace.
            
            The EventTarget.addEventListener() function should be used instead to add a listener for the event.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="em" type="inlineBaseType">
    <xs:annotation>
      <xs:documentation>
        Emphasis
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="strong" type="inlineBaseType">
    <xs:annotation>
      <xs:documentation>
        Strong emphasis
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="b" type="inlineBaseType">
    <xs:annotation>
      <xs:documentation>
        Bold
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="i" type="inlineBaseType">
    <xs:annotation>
      <xs:documentation>
        Italic
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="sup" type="inlineBaseType">
    <xs:annotation>
      <xs:documentation>
        Superscript
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="sub" type="inlineBaseType">
    <xs:annotation>
      <xs:documentation>
        Subscript
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="pre" type="inlineBaseType">
    <xs:annotation>
      <xs:documentation>
        The HTML <pre> Element (or HTML Preformatted Text) represents preformatted text. Text within this element is typically displayed in a non-proportional font exactly as it is laid out in the file. Whitespaces inside this element are displayed as typed.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="code" type="inlineBaseType">
    <xs:annotation>
      <xs:documentation>
        The HTML <code> Element represents a fragment of computer code. By default, it is displayed in the browser's default monospace font.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="kbd" type="inlineBaseType">
    <xs:annotation>
      <xs:documentation>
        The HTML <kbd> Element (or HTML Keyboard Input Element) represents user input and produces an inline element displayed in the browser's default monotype font.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="samp" type="inlineBaseType">
    <xs:annotation>
      <xs:documentation>
        The HTML <samp> element is an element intended to identify sample output from a computer program. It is usually displayed in the browser's default monotype font.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="cite" type="inlineBaseType">
    <xs:annotation>
      <xs:documentation>
        The HTML <cite> Element (or HTML Citation Element) represents a reference to a creative work. It must include the title of a work, the name of the author, or a URL reference, which may be in an abbreviated form according to the conventions used for the addition of citation metadata.
        
        Usage Notes:
        
        A creative work may include a book, a paper, an essay, a poem, a score, a song, a script, a film, a TV show, a game, a sculpture, a painting, a theater production, a play, an opera, a musical, an exhibition, a legal case report, a computer program, , a web site, a web page, a blog post or comment, a forum post or comment, a tweet, a written or oral statement, etc.
        Use the cite attribute on a <blockquote> or <q> element to reference an online resource for a source.
        
        
        Style note:
        
        To avoid the default italic style from being used for the <cite> element use the CSS font-style property.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="q">
    <xs:annotation>
      <xs:documentation>
        The HTML <q> Element (or HTML Quote Element) indicates that the enclosed text is a short inline quotation. This element is intended for short quotations that don't require paragraph breaks; for long quotations use <blockquote> element.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="inlines"/>
      </xs:choice>
      <xs:attribute name="cite" type="xs:anyURI">
        <xs:annotation>
          <xs:documentation>
            The value of this attribute is a URL that designates a source document or message for the information quoted. This attribute is intended to point to information explaining the context or the reference for the quote.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="tt" type="inlineBaseType">
    <xs:annotation>
      <xs:documentation>
        This feature is obsolete. Although it may still work in some browsers, its use is discouraged since it could be removed at any time. Try to avoid using it.
        
        The HTML Teletype Text Element (<tt>) produces an inline element displayed in the browser's default monotype font. This element was intended to style text as it would display on a fixed width display, such as a teletype. It probably is more common to display fixed width type using the <code> element.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="ins">
    <xs:annotation>
      <xs:documentation>
        The HTML <ins> Element (or HTML Inserted Text) HTML represents a range of text that has been added to a document.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="inlineBaseType">
          <xs:attribute name="cite" type="xs:anyURI"/>
          <xs:attribute name="datetime" type="xs:dateTime"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>
  <xs:element name="del">
    <xs:annotation>
      <xs:documentation>
        The HTML <del> element (or HTML Deleted Text Element) represents a range of text that has been deleted from a document. This element is often (but need not be) rendered with strike-through text.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="inlineBaseType">
          <xs:attribute name="cite" type="xs:anyURI"/>
          <xs:attribute name="datetime" type="xs:dateTime"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>
  <xs:element name="var" type="inlineBaseType">
    <xs:annotation>
      <xs:documentation>
        The HTML Variable Element (<var>) represents a variable in a mathematical expression or a programming context.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="small" type="inlineBaseType">
    <xs:annotation>
      <xs:documentation>
        The HTML Small Element (<small>) makes the text font size one size smaller (for example, from large to medium, or from small to x-small) down to the browser's minimum font size. In HTML5, this element is repurposed to represent side-comments and small print, including copyright and legal text, independent of its styled presentation.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="big" type="inlineBaseType">
    <xs:annotation>
      <xs:documentation>
        This feature is obsolete. Although it may still work in some browsers, its use is discouraged since it could be removed at any time. Try to avoid using it.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="br">
    <xs:annotation>
      <xs:documentation>
        The HTML <br> Element (or HTML Line Break Element) produces a line break in text (carriage-return). It is useful for writing a poem or an address, where the division of lines is significant.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
    </xs:complexType>
  </xs:element>
  <xs:element name="hr">
    <xs:annotation>
      <xs:documentation>
        The HTML <hr> element represents a thematic break between paragraph-level elements (for example, a change of scene in a story, or a shift of topic with a section). In previous versions of HTML, it represented a horizontal rule. It may still be displayed as a horizontal rule in visual browsers, but is now defined in semantic terms, rather than presentational terms.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attributeGroup ref="coreattrs"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="address">
    <xs:annotation>
      <xs:documentation>
        The HTML <address> Element may be used by authors to supply contact information for its nearest <article> or <body> ancestor; in the latter case, it applies to the whole document.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="coreattrs"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="blockquote">
    <xs:annotation>
      <xs:documentation>
        The HTML <blockquote> Element (or HTML Block Quotation Element) indicates that the enclosed text is an extended quotation. Usually, this is rendered visually by indentation (to change <blockquote> indent, use CSS margin property). A URL for the source of the quotation may be given using the cite attribute, while a text representation of the source can be given using the <cite> element.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="coreattrs"/>
      <xs:attribute name="cite" type="xs:anyURI"/>
      <xs:attribute name="align">
        <xs:simpleType>
          <xs:restriction base="xs:string">
            <xs:enumeration value="center"/>
            <xs:enumeration value="left"/>
            <xs:enumeration value="right"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:simpleType name="htmlLength">
    <xs:annotation>
      <xs:documentation>
        nn for pixels or nn% for percentage length
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string">
      <xs:pattern value="[\-+]?(\d+|\d+(\.\d+)?%)"/>
    </xs:restriction>
  </xs:simpleType>
  <xs:simpleType name="htmlLength-or-perl">
    <xs:union memberTypes="htmlLength perl"/>
  </xs:simpleType>
  <xs:element name="img">
    <xs:annotation>
      <xs:documentation>
        The HTML <img> Element (or HTML Image Element) represents an image of the document.
        
        Usage note:
        Browsers do not always display the image referenced by the element. This is the case for non-graphical browsers (including those used by people with vision impairments), or if the user chooses not to display images, or if the browser is unable to display the image because it is invalid or an unsupported type. In these cases, the browser may replace the image with the text defined in this element's alt attribute.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attributeGroup ref="coreattrs"/>
      <xs:attribute name="src" type="xs:anyURI" use="required">
        <xs:annotation>
          <xs:documentation>
            Image URL.
            On browsers supporting srcset, src is ignored if this one is provided.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="alt" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            This attribute defines the alternative text describing the image. Users will see this displayed if the image URL is wrong, the image is not in one of the supported formats, or until the image is downloaded.
            
            Usage note: Omitting this attribute indicates that the image is a key part of the content, but no textual equivalent is available. Setting this attribute to the empty string indicates that this image is not a key part of the content; non-visual browsers may omit it from rendering.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="width" type="htmlLength-or-perl">
        <xs:annotation>
          <xs:documentation>
            The width of the image in pixels or percent.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="height" type="htmlLength-or-perl">
        <xs:annotation>
          <xs:documentation>
            The height of the image in pixels or percent.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="TeXwidth">
        <xs:annotation>
          <xs:documentation>
            Allows you to set the width of the image, in mm or %, as it will be rendered into the LaTeX document used to print the problem.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:pattern value="[0-9]+(\.[0-9]+)?(\s*%)?"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="TeXheight" type="decimal-or-perl">
        <xs:annotation>
          <xs:documentation>
            Allows you to set the height of the image, in mm, as it will be rendered into the LaTeX document used to print the problem.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="align">
        <xs:annotation>
          <xs:documentation>
            This attribute is deprecated since HTML 4.01 and obsolete since HTML5. Use the vertical-align CSS property instead.
            
            Specifies the alignment of the image relative to the enclosing text paragraph:
            - bottom: The image will be aligned so that its bottom will be at the baseline of the surrounding text.
            - middle: The image will be aligned so that its center-line will be at the baseline of the surrounding text.
            - top: The image will be aligned so that its top will be at the baseline of the surrounding text.
            - left: The image will be placed so that it is at the left of the surrounding text. The surrounding text will fill in the region to the right of the image.
            - right: The image will be placed so that it is at the right of the surrounding text. The surrounding text will fill in the region to the left of the image.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="bottom"/>
                <xs:enumeration value="middle"/>
                <xs:enumeration value="top"/>
                <xs:enumeration value="left"/>
                <xs:enumeration value="right"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="TeXwrap">
        <xs:annotation>
          <xs:documentation>
            Allows you to select how the LaTeX document will attempt to wrap text around a horizontally aligned image.
            parbox: \newline and \parbox will be used to place the image. This method ensures that text will not be wrapped on top of the image, however very little text will appear next to the image itself.
            parpic: The picins package \parpic command will be used to place the image. This will wrap the remainder of the paragraph containing the picture around the image.
            If, however, there is insufficient text to fill the space to the left or right of the image, the next paragraph may be wrapped on top of the image. In addition, \parpic does not always honor the end of the page, causing the image to extend below the page footer.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="none"/>
                <xs:enumeration value="parbox"/>
                <xs:enumeration value="parpic"/>
                <xs:enumeration value="wrapfigure"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute default="no" name="encrypturl" type="yesno-or-perl"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="figure">
    <xs:annotation>
      <xs:documentation>
        The HTML <figure> Element represents self-contained content, frequently with a caption (<figcaption>), and is typically referenced as a single unit. While it is related to the main flow, its position is independent of the main flow. Usually this is an image, an illustration, a diagram, a code snippet, or a schema that is referenced in the main text, but that can be moved to another page or to an appendix without affecting the main flow.
        
        Usage note: A caption can be associated with the <figure> element by inserting a <figcaption> inside it (as the first or the last child).
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice minOccurs="0">
        <xs:sequence>
          <xs:element ref="figcaption"/>
          <xs:choice maxOccurs="unbounded" minOccurs="0">
            <xs:group ref="text-only"/>
          </xs:choice>
        </xs:sequence>
        <xs:sequence>
          <xs:choice maxOccurs="unbounded">
            <xs:group ref="text-only"/>
          </xs:choice>
          <xs:element minOccurs="0" ref="figcaption"/>
        </xs:sequence>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="figcaption">
    <xs:annotation>
      <xs:documentation>
        The HTML <figcaption> Element represents a caption or a legend associated with a figure or an illustration described by the rest of the data of the <figure> element which is its immediate ancestor which means <figcaption> can be the first or last element inside a <figure> block. Also, the HTML Figcaption Element is optional; if not provided, then the parent figure element will have no caption.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="object">
    <xs:annotation>
      <xs:documentation>
        The HTML <object> Element (or HTML Embedded Object Element) represents an external resource, which can be treated as an image, a nested browsing context, or a resource to be handled by a plugin.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="param"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="coreattrs"/>
      <xs:attribute name="classid" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            The URI of the object's implementation. It can be used together with, or in place of, the data attribute.
            Obsolete since HTML5.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="codebase" type="xs:anyURI">
        <xs:annotation>
          <xs:documentation>
            The base path used to resolve relative URIs specified by classid, data, or archive. If not specified, the default is the base URI of the current document.
            Obsolete since HTML5.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="data" type="xs:anyURI">
        <xs:annotation>
          <xs:documentation>
            The address of the resource as a valid URL. At least one of data and type must be defined.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="type" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            The content type of the resource specified by data. At least one of data and type must be defined.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="codetype" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            The content type of the data specified by classid.
            Obsolete since HTML5.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="archive">
        <xs:annotation>
          <xs:documentation>
            A space-separated list of URIs for archives of resources for the object.
            Obsolete since HTML5.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:list itemType="xs:anyURI"/>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="standby" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            A message that the browser can show while loading the object's implementation and data.
            Obsolete since HTML5.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="width" type="htmlLength-or-perl">
        <xs:annotation>
          <xs:documentation>
            The width of the display resource, in CSS pixels.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="height" type="htmlLength-or-perl">
        <xs:annotation>
          <xs:documentation>
            The height of the displayed resource, in CSS pixels.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="usemap" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            A hash-name reference to a <map> element; that is a '#' followed by the value of a name of a map element.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="name" type="xs:NMTOKEN">
        <xs:annotation>
          <xs:documentation>
            The name of valid browsing context (HTML5), or the name of the control (HTML 4).
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="param">
    <xs:annotation>
      <xs:documentation>
        param is used to supply a named property value
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="id" type="xs:ID"/>
      <xs:attribute name="name">
        <xs:annotation>
          <xs:documentation>
            Name of the parameter.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="value">
        <xs:annotation>
          <xs:documentation>
            Specifies the value of the parameter.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="data" name="valuetype">
        <xs:annotation>
          <xs:documentation>
            Obsolete in HTML5.
            
            Specifies the type of the value attribute.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="data"/>
            <xs:enumeration value="ref"/>
            <xs:enumeration value="object"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="type" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Obsolete in HTML5.
            
            Only used if the valuetype is set to "ref". Specifies the MIME type of values found at the URI specified by value.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="embed">
    <xs:annotation>
      <xs:documentation>
        The HTML <embed> Element represents an integration point for an external application or interactive content (in other words, a plug-in).
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="id" type="xs:ID"/>
      <xs:attribute name="src" type="xs:anyURI">
        <xs:annotation>
          <xs:documentation>
            The URL of the resource being embedded.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="type" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            The MIME type to use to select the plug-in to instantiate.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="width" type="non-negative-int-or-perl">
        <xs:annotation>
          <xs:documentation>
            The displayed width of the resource, in CSS pixels.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="height" type="non-negative-int-or-perl">
        <xs:annotation>
          <xs:documentation>
            The displayed height of the resource, in CSS pixels.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="applet">
    <xs:annotation>
      <xs:documentation>
        This feature is obsolete. Although it may still work in some browsers, its use is discouraged since it could be removed at any time. Try to avoid using it.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="param"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attribute name="id" type="xs:ID"/>
      <xs:attribute name="codebase" type="xs:anyURI"/>
      <xs:attribute name="archive" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Comma-separated list of URIs for archives containing classes and other resources that will be "preloaded".
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="code"/>
      <xs:attribute name="object"/>
      <xs:attribute name="alt" type="xs:string"/>
      <xs:attribute name="name" type="xs:NMTOKEN"/>
      <xs:attribute name="width" type="non-negative-int-or-perl" use="required"/>
      <xs:attribute name="height" type="non-negative-int-or-perl" use="required"/>
      <xs:attribute name="align">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="top"/>
            <xs:enumeration value="middle"/>
            <xs:enumeration value="bottom"/>
            <xs:enumeration value="left"/>
            <xs:enumeration value="right"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="hspace" type="xs:nonNegativeInteger"/>
      <xs:attribute name="vspace" type="xs:nonNegativeInteger"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="video">
    <xs:annotation>
      <xs:documentation>
        The HTML <video> element is used to embed video content. It may contain several video sources, represented using the src attribute or the <source> element; the browser will choose the most suitable one.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="source"/>
      </xs:choice>
      <xs:attributeGroup ref="coreattrs"/>
      <xs:attribute name="src" type="xs:anyURI"/>
      <xs:attribute name="width" type="non-negative-int-or-perl"/>
      <xs:attribute name="height" type="non-negative-int-or-perl"/>
      <xs:attribute name="autoplay">
        <xs:simpleType>
          <xs:restriction base="xs:string">
            <xs:enumeration value="autoplay"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="controls">
        <xs:simpleType>
          <xs:restriction base="xs:string">
            <xs:enumeration value="controls"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="loop">
        <xs:simpleType>
          <xs:restriction base="xs:string">
            <xs:enumeration value="loop"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="source">
    <xs:annotation>
      <xs:documentation>
        The HTML <source> element is used to specify multiple media resources for <picture>, <audio> and <video> elements. It is an empty element. It is commonly used to serve the same media in multiple formats supported by different browsers.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="src" type="xs:anyURI" use="required"/>
      <xs:attribute name="type" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="audio">
    <xs:annotation>
      <xs:documentation>
        The HTML <audio> element is used to embed sound content in documents. It may contain several audio sources, represented using the src attribute or the <source> element; the browser will choose the most suitable one.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="source"/>
      </xs:choice>
      <xs:attributeGroup ref="coreattrs"/>
      <xs:attribute name="src" type="xs:anyURI"/>
      <xs:attribute name="autoplay">
        <xs:simpleType>
          <xs:restriction base="xs:string">
            <xs:enumeration value="autoplay"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="controls">
        <xs:simpleType>
          <xs:restriction base="xs:string">
            <xs:enumeration value="controls"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="loop">
        <xs:simpleType>
          <xs:restriction base="xs:string">
            <xs:enumeration value="loop"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  
  <xs:element name="map">
    <xs:annotation>
      <xs:documentation>
        The HTML <map> element is used with <area> elements to define an image map (a clickable link area).
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice>
        <xs:choice maxOccurs="unbounded">
          <xs:group ref="blocks-with-text"/>
        </xs:choice>
        <xs:element maxOccurs="unbounded" ref="area"/>
      </xs:choice>
      <xs:attribute name="id" type="xs:ID" use="required"/>
      <xs:attribute name="class" type="xs:NMTOKENS"/>
      <xs:attribute name="style" type="xs:string"/>
      <xs:attribute name="title" type="xs:string"/>
      <xs:attribute name="name" type="xs:NMTOKEN"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="area">
    <xs:annotation>
      <xs:documentation>
        The HTML <area> element defines a hot-spot region on an image, and optionally associates it with a hypertext link. This element is used only within a <map> element.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute default="rect" name="shape">
        <xs:annotation>
          <xs:documentation>
            The shape of the associated hot spot. The specifications for HTML 5 and HTML 4 define the values rect, which defines a rectangular region; circle, which defines a circular region; poly, which defines a polygon; and default, which indicates the entire region beyond any defined shapes.
            Many browsers, notably Internet Explorer 4 and higher, support circ, polygon, and rectangle as valid values for shape; these values are *not standard*.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="rect"/>
            <xs:enumeration value="circle"/>
            <xs:enumeration value="poly"/>
            <xs:enumeration value="default"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="coords">
        <xs:annotation>
          <xs:documentation>
            A set of values specifying the coordinates of the hot-spot region. The number and meaning of the values depend upon the value specified for the shape attribute. For a rect or rectangle shape, the coords value is two x,y pairs: left, top, right, and bottom. For a circle shape, the value is x,y,r where x,y is a pair specifying the center of the circle and r is a value for the radius. For a poly or polygon< shape, the value is a set of x,y pairs for each point in the polygon: x1,y1,x2,y2,x3,y3, and so on. In HTML4, the values are numbers of pixels or percentages, if a percent sign (%) is appended; in HTML5, the values are numbers of CSS pixels.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:restriction base="xs:string">
            <xs:pattern value="[\-+]?(\d+|\d+(\.\d+)?%)(,\s*[\-+]?(\d+|\d+(\.\d+)?%))*"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="href" type="xs:anyURI">
        <xs:annotation>
          <xs:documentation>
            The hyperlink target for the area. Its value is a valid URL. In HTML4, either this attribute or the nohref attribute must be present in the element. In HTML5, this attribute may be omitted; if so, the area element does not represent a hyperlink.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="nohref">
        <xs:annotation>
          <xs:documentation>
            Indicates that no hyperlink exists for the associated area. Either this attribute or the href attribute must be present in the element.
            
            Usage note: This attribute is obsolete in HTML5, instead omitting the href attribute is sufficient.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="nohref"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="alt" type="xs:string" use="required">
        <xs:annotation>
          <xs:documentation>
            A text string alternative to display on browsers that do not display images. The text should be phrased so that it presents the user with the same kind of choice as the image would offer when displayed without the alternative text. In HTML4, this attribute is required, but may be the empty string (""). In HTML5, this attribute is required only if the href attribute is used.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="canvas">
    <xs:annotation>
      <xs:documentation>
        The HTML <canvas> Element can be used to draw graphics via scripting (usually JavaScript). For example, it can be used to draw graphs, make photo compositions or even perform animations. You may (and should) provide alternate content inside the <canvas> block. That content will be rendered both on older browsers that don't support canvas and in browsers with JavaScript disabled.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="blocks-with-text"/>
        <xs:group ref="inlines"/>
      </xs:choice>
      <xs:attributeGroup ref="coreattrs"/>
      <xs:attribute default="300" name="width" type="htmlLength-or-perl">
        <xs:annotation>
          <xs:documentation>
            The width of the coordinate space in CSS pixels.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="150" name="height" type="htmlLength-or-perl">
        <xs:annotation>
          <xs:documentation>
            The height of the coordinate space in CSS pixels.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  
  <xs:element name="form">
    <xs:annotation>
      <xs:documentation>
        The HTML <form> element represents a document section that contains interactive controls to submit information to a web server.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="blocks-with-text"/>
      </xs:choice>
      <xs:attributeGroup ref="coreattrs"/>
      <xs:attribute name="action" type="xs:anyURI">
        <xs:annotation>
          <xs:documentation>
            The URI of a program that processes the form information.
            
            In HTML5, the action attribute is no longer required.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="get" name="method">
        <xs:annotation>
          <xs:documentation>
            The HTTP method that the browser uses to submit the form. Possible values are:
            
            - post: Corresponds to the HTTP POST method ; form data are included in the body of the form and sent to the server.
            
            - get: Corresponds to the HTTP GET method; form data are appended to the action attribute URI with a '?' as separator, and the resulting URI is sent to the server. Use this method when the form has no side-effects and contains only ASCII characters.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="get"/>
            <xs:enumeration value="post"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute default="application/x-www-form-urlencoded" name="enctype" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            When the value of the method attribute is post, enctype is the MIME type of content that is used to submit the form to the server. Possible values are:
            
            - application/x-www-form-urlencoded: The default value if the attribute is not specified.
            - multipart/form-data: The value used for an <input> element with the type attribute set to "file".
            - text/plain (HTML5)
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="accept-charset" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            A space- or comma-delimited list of character encodings that the server accepts. The browser uses them in the order in which they are listed. The default value, the reserved string "UNKNOWN", indicates the same encoding as that of the document containing the form element.
            
            In previous versions of HTML, the different character encodings could be delimited by spaces or commas. In HTML5, only spaces are allowed as delimiters.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="name" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            The name of the form. In HTML 4, its use is deprecated (id should be used instead). It must be unique among the forms in a document and not just an empty string in HTML 5.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="accept" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            A comma-separated list of content types that the server accepts.
            
            Usage note: This attribute has been removed in HTML5 and should no longer be used. Instead, use the accept attribute of the specific <input> element.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="onsubmit" type="xs:string"/>
      <xs:attribute name="onreset" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="label">
    <xs:annotation>
      <xs:documentation>
        The HTML <label> Element represents a caption for an item in a user interface. It can be associated with a control either by placing the control element inside the label element, or by using the for attribute. Such a control is called the labeled control of the label element.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="inlineBaseType">
          <xs:attribute name="for" type="xs:IDREF"/>
          <xs:attribute name="accesskey">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:length fixed="true" value="1"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:attribute>
          <xs:attribute name="onfocus" type="xs:string"/>
          <xs:attribute name="onblur" type="xs:string"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>
  <xs:simpleType name="InputType">
    <xs:restriction base="xs:token">
      <xs:enumeration value="text"/>
      <xs:enumeration value="password"/>
      <xs:enumeration value="checkbox"/>
      <xs:enumeration value="radio"/>
      <xs:enumeration value="submit"/>
      <xs:enumeration value="reset"/>
      <xs:enumeration value="file"/>
      <xs:enumeration value="hidden"/>
      <xs:enumeration value="image"/>
      <xs:enumeration value="button"/>
    </xs:restriction>
  </xs:simpleType>
  <xs:element name="input">
    <xs:annotation>
      <xs:documentation>
        The HTML <input> element is used to create interactive controls for web-based forms in order to accept data from user. 
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attributeGroup ref="coreattrs"/>
      <xs:attribute default="text" name="type" type="InputType"/>
      <xs:attribute name="name">
        <xs:annotation>
          <xs:documentation>
            the name attribute is required for all but submit & reset
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="value"/>
      <xs:attribute name="checked">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="checked"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="disabled">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="disabled"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="readonly">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="readonly"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="size"/>
      <xs:attribute name="maxlength" type="xs:nonNegativeInteger"/>
      <xs:attribute name="src" type="xs:anyURI"/>
      <xs:attribute name="alt"/>
      <xs:attribute name="usemap" type="xs:anyURI"/>
      <xs:attribute name="onselect" type="xs:string"/>
      <xs:attribute name="onchange" type="xs:string"/>
      <xs:attribute name="accept" type="xs:string"/>
      <xs:attribute name="onclick" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Javascript event handler content attribute for the "click" event.
            
            Warning: event handler content attributes should be avoided. They make the markup bigger and less readable. Concerns of content/structure and behavior are not well-separated, making a bug harder to find. Furthermore, usage of event attributes almost always causes scripts to expose global functions on the Window object, polluting the global namespace.
            
            The EventTarget.addEventListener() function should be used instead to add a listener for the event.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="select">
    <xs:annotation>
      <xs:documentation>
        The HTML select (<select>) element represents a control that presents a menu of options. The options within the menu are represented by <option> elements, which can be grouped by <optgroup> elements. Options can be pre-selected for the user.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded">
        <xs:element ref="optgroup"/>
        <xs:element ref="option"/>
      </xs:choice>
      <xs:attribute name="name"/>
      <xs:attribute name="size" type="xs:nonNegativeInteger"/>
      <xs:attribute name="multiple">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="multiple"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="disabled">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="disabled"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="tabindex" type="xs:nonNegativeInteger"/>
      <xs:attribute name="onfocus" type="xs:string"/>
      <xs:attribute name="onblur" type="xs:string"/>
      <xs:attribute name="onchange" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="optgroup">
    <xs:annotation>
      <xs:documentation>
        In a Web form, the HTML <optgroup> element creates a grouping of options within a <select> element.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" ref="option"/>
      </xs:sequence>
      <xs:attribute name="disabled">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="disabled"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="label" type="xs:string" use="required"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="option">
    <xs:annotation>
      <xs:documentation>
        In a Web form, the HTML <option> element is used to create a control representing an item within a <select>, an <optgroup> or a <datalist> HTML5 element.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:attribute name="selected">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="selected"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="disabled">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="disabled"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="label" type="xs:string"/>
      <xs:attribute name="value"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="textarea">
    <xs:annotation>
      <xs:documentation>
        The HTML <textarea> element represents a multi-line plain-text editing control.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:attribute name="name"/>
      <xs:attribute name="rows" type="xs:nonNegativeInteger" use="required"/>
      <xs:attribute name="cols" type="xs:nonNegativeInteger" use="required"/>
      <xs:attribute name="disabled">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="disabled"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="readonly">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="readonly"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="onselect" type="xs:string"/>
      <xs:attribute name="onchange" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="fieldset">
    <xs:annotation>
      <xs:documentation>
        The HTML <fieldset> element is used to group several controls as well as labels (<label>) within a web form.
        
        Only one legend element should occur in the content, and if present should only be preceded by whitespace.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:sequence>
        <xs:element ref="legend"/>
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-only"/>
        </xs:choice>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="legend">
    <xs:annotation>
      <xs:documentation>
        The HTML <legend> Element (or HTML Legend Field Element) represents a caption for the content of its parent <fieldset>.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="inlineBaseType">
          <xs:attribute name="accesskey">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:length fixed="true" value="1"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:attribute>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>
  <xs:element name="button">
    <xs:annotation>
      <xs:documentation>
        The HTML <button> Element represents a clickable button.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attribute name="name"/>
      <xs:attribute name="value"/>
      <xs:attribute default="submit" name="type">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="button"/>
            <xs:enumeration value="submit"/>
            <xs:enumeration value="reset"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="disabled">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="disabled"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  
  <xs:element name="iframe">
    <xs:annotation>
      <xs:documentation>
        The HTML <iframe> Element (or HTML inline frame element) represents a nested browsing context, effectively embedding another HTML page into the current page.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:attributeGroup ref="coreattrs"/>
      <xs:attribute name="name" type="xs:NMTOKEN"/>
      <xs:attribute name="src" type="xs:anyURI"/>
      <xs:attribute default="1" name="frameborder">
        <xs:annotation>
          <xs:documentation>
            Warning: HTML 4 only
            
            The value 1 (the default) tells the browser to draw a border between this frame and every other frame.
            The value 0 tells the browser not to draw a border between this frame and other frames.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="1"/>
            <xs:enumeration value="0"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="height" type="htmlLength"/>
      <xs:attribute name="width" type="htmlLength"/>
      <xs:attribute name="allowfullscreen">
        <xs:simpleType>
          <xs:restriction base="xs:string">
            <xs:enumeration value="allowfullscreen"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  
</xs:schema>
Index: modules/damieng/clean_xml/post_xml.pm
+++ modules/damieng/clean_xml/post_xml.pm
#!/usr/bin/perl

package post_xml;

use strict;
use utf8;
use warnings;

use File::Basename;
use File::Temp qw/ tempfile /;
use Cwd 'abs_path';
use XML::LibXML;
use HTML::TokeParser; # used to parse sty files
use Tie::IxHash; # for ordered hashes

use Env qw(RES_DIR); # path of res directory parent (without the / at the end)

no warnings 'recursion'; # yes, fix_paragraph is using heavy recursion, I know

# these are constants
my @block_elements = ('parameter','location','answer','foil','image','polygon','rectangle','text','conceptgroup','itemgroup','item','label','data','function','array','unit','answergroup','functionplotresponse','functionplotruleset','functionplotelements','functionplotcustomrule','essayresponse','hintpart','formulahint','numericalhint','reactionhint','organichint','optionhint','radiobuttonhint','stringhint','customhint','mathhint','formulahintcondition','numericalhintcondition','reactionhintcondition','organichintcondition','optionhintcondition','radiobuttonhintcondition','stringhintcondition','customhintcondition','mathhintcondition','imageresponse','foilgroup','datasubmission','textfield','hiddensubmission','radiobuttonresponse','rankresponse','matchresponse','import','style','script','window','block','library','notsolved','part','postanswerdate','preduedate','problem','problemtype','randomlabel','bgimg','labelgroup','randomlist','solved','while','tex','print','web','gnuplo!
 t','curve','Task','IntroParagraph','ClosingParagraph','Question','QuestionText','Setup','Instance','InstanceText','Criteria','CriteriaText','GraderNote','languageblock','translated','lang','instructorcomment','dataresponse','togglebox','standalone','comment','drawimage','allow','displayduedate','displaytitle','responseparam','organicstructure','scriptlib','parserlib','drawoptionlist','spline','backgroundplot','plotobject','plotvector','drawvectorsum','functionplotrule','functionplotvectorrule','functionplotvectorsumrule','axis','key','xtics','ytics','title','xlabel','ylabel','hiddenline','dtm');
my @inline_like_block = ('stringresponse','optionresponse','numericalresponse','formularesponse','mathresponse','organicresponse','reactionresponse','customresponse','externalresponse', 'hint', 'hintgroup'); # inline elements treated like blocks for pretty print and some other things
my @responses = ('stringresponse','optionresponse','numericalresponse','formularesponse','mathresponse','organicresponse','reactionresponse','customresponse','externalresponse','essayresponse','radiobuttonresponse','matchresponse','rankresponse','imageresponse','functionplotresponse');
my @block_html = ('html','head','body','section','h1','h2','h3','h4','h5','h6','div','p','ul','ol','li','table','tbody','tr','td','th','dl','dt','dd','pre','noscript','hr','address','blockquote','object','applet','embed','map','form','fieldset','iframe','center','frameset');
my @no_newline_inside = ('import','parserlib','scriptlib','data','function','label','xlabel','ylabel','tic','text','rectangle','image','title','h1','h2','h3','h4','h5','h6','li','td','p');
my @preserve_elements = ('script','answer','pre');
my @accepting_style = ('section','h1','h2','h3','h4','h5','h6','div','p','li','td','th','dt','dd','pre','blockquote');
my @latex_math = ('\alpha', '\theta', '\omicron', '\tau', '\beta', '\vartheta', '\pi', '\upsilon', '\gamma', '\gamma', '\varpi', '\phi', '\delta', '\kappa', '\rho', '\varphi', '\epsilon', '\lambda', '\varrho', '\chi', '\varepsilon', '\mu', '\sigma', '\psi', '\zeta', '\nu', '\varsigma', '\omega', '\eta', '\xi',
  '\Gamma', '\Lambda', '\Sigma', '\Psi', '\Delta', '\Xi', '\Upsilon', '\Omega', '\Theta', '\Pi', '\Phi',
  '\pm', '\cap', '\diamond', '\oplus', '\mp', '\cup', '\bigtriangleup', '\ominus', '\times', '\uplus', '\bigtriangledown', '\otimes', '\div', '\sqcap', '\triangleleft', '\oslash', '\ast', '\sqcup', '\triangleright', '\odot', '\star', '\vee', '\lhd$', '\bigcirc', '\circ', '\wedge', '\rhd$', '\dagger', '\bullet', '\setminus', '\unlhd$', '\ddagger', '\cdot', '\wr', '\unrhd$', '\amalg', '+', '-',
  '\leq', '\geq', '\equiv', '\models', '\prec', '\succ', '\sim', '\perp', '\preceq', '\succeq', '\simeq', '\mid', '\ll', '\gg', '\asymp', '\parallel', '\subset', '\supset', '\approx', '\bowtie', '\subseteq', '\supseteq', '\cong', '\Join$', '\sqsubset$', '\sqsupset$', '\neq', '\smile', '\sqsubseteq', '\sqsupseteq', '\doteq', '\frown', '\in', '\ni', '\propto', '\vdash', '\dashv',
  '\colon', '\ldotp', '\cdotp',
  '\leftarrow', '\longleftarrow', '\uparrow', '\Leftarrow', '\Longleftarrow', '\Uparrow', '\rightarrow', '\longrightarrow', '\downarrow', '\Rightarrow', '\Longrightarrow', '\Downarrow', '\leftrightarrow', '\longleftrightarrow', '\updownarrow', '\Leftrightarrow', '\Longleftrightarrow', '\Updownarrow', '\mapsto', '\longmapsto', '\nearrow', '\hookleftarrow', '\hookrightarrow', '\searrow', '\leftharpoonup', '\rightharpoonup', '\swarrow', '\leftharpoondown', '\rightharpoondown', '\nwarrow', '\rightleftharpoons', '\leadsto$',
  '\ldots', '\cdots', '\vdots', '\ddots', '\aleph', '\prime', '\forall', '\infty', '\hbar', '\emptyset', '\exists', '\Box$', '\imath', '\nabla', '\neg', '\Diamond$', '\jmath', '\surd', '\flat', '\triangle', '\ell', '\top', '\natural', '\clubsuit', '\wp', '\bot', '\sharp', '\diamondsuit', '\Re', '\|', '\backslash', '\heartsuit', '\Im', '\angle', '\partial', '\spadesuit', '\mho$',
  '\sum', '\bigcap', '\bigodot', '\prod', '\bigcup', '\bigotimes', '\coprod', '\bigsqcup', '\bigoplus', '\int', '\bigvee', '\biguplus', '\oint', '\bigwedge',
  '\arccos', '\cos', '\csc', '\exp', '\ker', '\limsup', '\min', '\sinh', '\arcsin', '\cosh', '\deg', '\gcd', '\lg', '\ln', '\Pr', '\sup', '\arctan', '\cot', '\det', '\hom', '\lim', '\log', '\sec', '\tan', '\arg', '\coth', '\dim', '\inf', '\liminf', '\max', '\sin', '\tanh',
  '\uparrow', '\Uparrow', '\downarrow', '\Downarrow', '\updownarrow', '\Updownarrow', '\lfloor', '\rfloor', '\lceil', '\rceil', '\langle', '\rangle', '\backslash',
  '\rmoustache', '\lmoustache', '\rgroup', '\lgroup', '\arrowvert', '\Arrowvert', '\bracevert',
  '\hat{', '\acute{', '\bar{', '\dot{', '\breve{', '\check{', '\grave{', '\vec{', '\ddot{', '\tilde{',
  '\widetilde{', '\widehat{', '\overleftarrow{', '\overrightarrow{', '\overline{', '\underline{', '\overbrace{', '\underbrace{', '\sqrt{', '\sqrt[', '\frac{'
);
# list of elements that can contain style elements:
my @containing_styles = ('library','problem', at responses,'foil','item','text','hintgroup','hintpart','label','part','preduedate','postanswerdate','solved','notsolved','block','while','web','standalone','problemtype','languageblock','translated','lang','window','windowlink','togglebox','instructorcomment','body','section','div','p','li','dd','td','th','blockquote','object','applet','video','audio','canvas','fieldset','button',
'span','strong','em','b','i','sup','sub','code','kbd','samp','tt','ins','del','var','small','big','u','font');
my @html_styles = ('span', 'strong', 'em' , 'b', 'i', 'sup', 'sub', 'tt', 'var', 'small', 'big', 'u');


# Parses the XML document and fixes many things to turn it into a LON-CAPA 3 document
# Returns the text of the document.
sub post_xml {
  my ($textref, $new_path) = @_;
  
  my $dom_doc = XML::LibXML->load_xml(string => $textref);

  my $root = fix_structure($dom_doc);

  remove_elements($root, ['startouttext','startoutext','startottext','startouttex','startouttect','atartouttext','starouttext','starttextout','starttext','starttextarea','endouttext','endoutext','endoutttext','endouttxt','endouutext','ednouttext','endouttex','endoouttext','endouttest','endtextout','endtextarea','startpartmarker','endpartmarker','basefont','x-claris-tagview','x-claris-window','x-sas-window']);
  
  remove_empty_attributes($root);
  
  fix_attribute_case($root);
  
  my $fix_by_hand = replace_m($root);
  
  my @all_block = (@block_elements, @block_html);
  add_sty_blocks($new_path, $root, \@all_block); # must come before the subs using @all_block

  fix_block_styles($root, \@all_block);
  $root->normalize();
  
  fix_fonts($root, \@all_block);
  
  replace_u($root);

  remove_bad_cdata_sections($root);
  
  add_cdata_sections($root);
  
  fix_style_element($root);
  
  fix_tables($root);

  fix_lists($root);
  
  fix_wrong_name_for_img($root); # should be before replace_deprecated_attributes_by_css

  replace_deprecated_attributes_by_css($root);
  
  replace_center($root, \@all_block); # must come after replace_deprecated_attributes_by_css
  
  replace_nobr($root);
  
  remove_useless_notsolved($root);
  
  fix_paragraphs_inside($root, \@all_block);

  remove_empty_style($root);
  
  fix_empty_lc_elements($root);
  
  lowercase_attribute_values($root);
  
  replace_numericalresponse_unit_attribute($root);
  
  replace_functions_by_elements($root);
  
  pretty($root, \@all_block);

  replace_tm_dtm($root);
  
  open my $out, '>', $new_path;
  print $out $dom_doc->toString(); # byte string !
  close $out;
  
  if ($fix_by_hand) {
    die "The file has been converted but it should be fixed by hand.";
  }
}

sub fix_structure {
  my ($doc) = @_;
  # the 'loncapa' root element has already been added in pre_xml
  my $root = $doc->documentElement;
  # inside the root, replace html, problem and library elements by their content
  my @toreplace = ('html','problem','library');
  foreach my $name (@toreplace) {
    my @elements = $root->getElementsByTagName($name);
    foreach my $element (@elements) {
      replace_by_children($element);
    }
  }
  # insert all link and style elements inside a new head element
  my $current_node = undef;
  my @heads = $doc->getElementsByTagName('head');
  my @links = $doc->getElementsByTagName('link');
  my @styles = $doc->getElementsByTagName('style');
  my @titles = $doc->getElementsByTagName('title');
  if (scalar(@titles) > 0) {
    # NOTE: there is a title element in gnuplot, not to be confused with the one inside HTML head
    for (my $i=0; $i<scalar(@titles); $i++) {
      my $title = $titles[$i];
      my $found_gnuplot = 0;
      my $ancestor = $title->parentNode;
      while (defined $ancestor) {
        if ($ancestor->nodeName eq 'gnuplot') {
          $found_gnuplot = 1;
          last;
        }
        $ancestor = $ancestor->parentNode;
      }
      if ($found_gnuplot) {
        splice(@titles, $i, 1);
        $i--;
      }
    }
  }
  if (scalar(@heads) > 0 || scalar(@titles) > 0 || scalar(@links) > 0 || scalar(@styles) > 0) {
    my $htmlhead = $doc->createElement('head');
    foreach my $head (@heads) {
      my $next;
      for (my $child=$head->firstChild; defined $child; $child=$next) {
        $next = $child->nextSibling;
        $head->removeChild($child);
        if ($child->nodeType != XML_ELEMENT_NODE ||
            string_in_array(['title','script','style','meta','link','import','base'], $child->nodeName)) {
          $htmlhead->appendChild($child);
        } else {
          # this should not be in head
          insert_after_or_first($root, $child, $current_node);
        }
      }
      $head->parentNode->removeChild($head);
    }
    foreach my $child (@titles, @links, @styles) {
      $child->parentNode->removeChild($child);
      $htmlhead->appendChild($child);
    }
    insert_after_or_first($root, $htmlhead, $current_node);
    $current_node = $htmlhead;
  }
  # body
  my $htmlbody = undef;
  my @bodies = $doc->getElementsByTagName('body');
  if (scalar(@bodies) > 0) {
    # TODO: fix content and position of body elements
    if ($root->nodeName eq 'problem') {
      foreach my $body (@bodies) {
        replace_by_children($body);
      }
    }
  }
  # add all the meta elements afterwards when they are LON-CAPA meta. Remove all HTML meta.
  my @meta_names = ('abstract','author','authorspace','avetries','avetries_list','clear','comefrom','comefrom_list','copyright','correct','count','course','course_list','courserestricted','creationdate','dependencies','depth','difficulty','difficulty_list','disc','disc_list','domain','end','field','firstname','generation','goto','goto_list','groupname','helpful','highestgradelevel','hostname','id','keynum','keywords','language','lastname','lastrevisiondate','lowestgradelevel','middlename','mime','modifyinguser','notes','owner','permanentemail','scope','sequsage','sequsage_list','standards','start','stdno','stdno_list','subject','technical','title','url','username','value','version');
  my @metas = $doc->getElementsByTagName('meta');
  foreach my $meta (@metas) {
    $meta->parentNode->removeChild($meta);
    my $name = $meta->getAttribute('name');
    my $content = $meta->getAttribute('content');
    if (defined $name && defined $content && string_in_array(\@meta_names, lc($name))) {
      my $lcmeta = $doc->createElement('meta');
      $lcmeta->setAttribute('name', lc($name));
      $lcmeta->setAttribute('content', $content);
      insert_after_or_first($root, $lcmeta, $current_node);
      $current_node = $lcmeta;
    }
  }
  return($root);
}

# insert the new child under parent after the reference child, or as the first child if the reference child is not defined
sub insert_after_or_first {
  my ($parent, $newchild, $refchild) = @_;
  if (defined $refchild) {
    $parent->insertAfter($newchild, $refchild);
  } elsif (defined $parent->firstChild) {
    $parent->insertBefore($newchild, $parent->firstChild);
  } else {
    $parent->appendChild($newchild);
  }
}

# removes all elements with given names inside the node, but keep the content
sub remove_elements {
  my ($node, $to_remove) = @_;
  my $nextChild;
  for (my $child=$node->firstChild; defined $child; $child=$nextChild) {
    $nextChild = $child->nextSibling;
    my $type = $node->nodeType;
    if ($type == XML_ELEMENT_NODE) {
      if (string_in_array($to_remove, $child->nodeName)) {
        my $first_non_white = $child->firstChild;
        if (defined $first_non_white && $first_non_white->nodeType == XML_TEXT_NODE &&
            $first_non_white->nodeValue =~ /^\s*$/) {
          $first_non_white = $first_non_white->nextSibling;
        }
        if (defined $first_non_white) {
          $nextChild = $first_non_white;
          replace_by_children($child);
        } else {
          $node->removeChild($child);
        }
      } else {
        remove_elements($child, $to_remove);
      }
    }
  }
}

# removes some attributes that have an invalid empty value
sub remove_empty_attributes {
  my ($root) = @_;
  my $doc = $root->ownerDocument;
  # this list is based on validation errors in the MSU subset (it could be more complete if it was based on the schema)
  my @attributes = (
    ['curve', ['pointsize']],
    ['foil', ['location']],
    ['foilgroup', ['checkboxoptions', 'options', 'texoptions']],
    ['gnuplot', ['pattern', 'texwidth']],
    ['img', ['height', 'texheight', 'texwidth', 'texwrap', 'width']],
    ['import', ['importmode']],
    ['optionresponse', ['max']],
    ['organicstructure', ['options']],
    ['radiobuttonresponse', ['max']],
    ['randomlabel', ['height', 'texwidth', 'width']],
    ['stringresponse', ['type']],
    ['textline', ['size']],
  );
  foreach my $element_attributes (@attributes) {
    my $element_name = $element_attributes->[0];
    my $attribute_names = $element_attributes->[1];
    my @elements = $doc->getElementsByTagName($element_name);
    foreach my $element (@elements) {
      foreach my $attribute_name (@$attribute_names) {
        my $value = $element->getAttribute($attribute_name);
        if (defined $value && $value =~ /^\s*$/) {
          $element->removeAttribute($attribute_name);
        }
      }
    }
  }
}

# fixes the case for a few attributes that are not all lowercase
# (the HTML parser used in html_to_xml turns everything lowercase, which is a good thing in general)
sub fix_attribute_case {
  my ($root) = @_;
  my $doc = $root->ownerDocument;
  my @attributes = (
    ['labelgroup', ['TeXsize']],
    ['h1', ['TeXsize']],
    ['h2', ['TeXsize']],
    ['h3', ['TeXsize']],
    ['h4', ['TeXsize']],
    ['h5', ['TeXsize']],
    ['h6', ['TeXsize']],
    # font and basefont have a TeXsize but will be removed
    ['optionresponse', ['TeXlayout']],
    ['itemgroup', ['TeXitemgroupwidth']],
    ['Task', ['OptionalRequired']],
    ['Question', ['OptionalRequired','Mandatory']],
    ['Instance', ['OptionalRequired','Disabled']],
    ['Criteria', ['Mandatory']],
    ['table', ['TeXwidth','TeXtheme']],
    ['td', ['TeXwidth']],
    ['th', ['TeXwidth']],
    ['img', ['TeXwidth','TeXheight','TeXwrap']],
  );
  foreach my $element_attributes (@attributes) {
    my $element_name = $element_attributes->[0];
    my $attribute_names = $element_attributes->[1];
    my @elements = $doc->getElementsByTagName($element_name);
    foreach my $element (@elements) {
      foreach my $attribute_name (@$attribute_names) {
        my $value = $element->getAttribute(lc($attribute_name));
        if (defined $value) {
          $element->removeAttribute(lc($attribute_name));
          $element->setAttribute($attribute_name, $value);
        }
      }
    }
  }
}

# Replaces m by HTML, tm and/or dtm (which will be replaced by <m> later, but they are useful
#   to know if the element is a block element or not).
# m might contain non-math LaTeX, while tm and dtm may only contain math.
# Returns 1 if the file should be fixed by hand, 0 otherwise.
sub replace_m {
  my ($root) = @_;
  my $doc = $root->ownerDocument;
  my $fix_by_hand = 0;
  # search for variable declarations
  my @variables = ();
  my @scripts = $root->getElementsByTagName('script');
  foreach my $script (@scripts) {
    my $type = $script->getAttribute('type');
    if (defined $type && $type eq 'loncapa/perl') {
      if (defined $script->firstChild && $script->firstChild->nodeType == XML_TEXT_NODE) {
        my $text = $script->firstChild->nodeValue;
        # NOTE: we are not interested in replacing "@value", only "$value"
        # this regexp is for "  $a = ..." and "  $a[...] = ..."
        while ($text =~ /^[ \t]*\$([a-zA-Z_0-9]+)(?:\[[^\]]+\])?[ \t]*=/gm) {
          if (!string_in_array(\@variables, $1)) {
            push(@variables, $1);
          }
        }
        # this regexp is for "...;  $a = ..." and "...;  $a[...] = ..."
        while ($text =~ /^[^'"\/;]+;[ \t]*\$([a-zA-Z_0-9]+)(?:\[[^\]]+\])?[ \t]*=/gm) {
          if (!string_in_array(\@variables, $1)) {
            push(@variables, $1);
          }
        }
        # this regexp is for "  ($a, $b, $c) = ..."
        my @matches = ($text =~ /^[ \t]*\([ \t]*\$([a-zA-Z_0-9]+)(?:[ \t]*,[ \t]*\$([a-zA-Z_0-9]+))*[ \t]*\)[ \t]*=/gm);
        foreach my $match (@matches) {
          if (!defined $match) {
            next; # not sure why it happens, but it does
          }
          if (!string_in_array(\@variables, $match)) {
            push(@variables, $match);
          }
        }
        # and this one is for "push @a"
        while ($text =~ /^[ \t]*push @([a-zA-Z_0-9]+)[ \t,]*/gm) {
          if (!string_in_array(\@variables, $1)) {
            push(@variables, $1);
          }
        }
        # use the opportunity to report usage of <m> in Perl scripts
        if ($text =~ /^[^#].*<m[ >]/m) {
          print "WARNING: <m> is used in a script, it should be converted by hand\n";
          $fix_by_hand = 1;
        }
      }
    }
  }
  my @ms = $root->getElementsByTagName('m');
  foreach my $m (@ms) {
    if (!defined $m->firstChild) {
      $m->parentNode->removeChild($m);
      next;
    }
    if (defined $m->firstChild->nextSibling || $m->firstChild->nodeType != XML_TEXT_NODE) {
      print "WARNING: m value is not simple text\n";
      $fix_by_hand = 1;
      next;
    }
    my $text = $m->firstChild->nodeValue;
    my $text_before_variable_replacement = $text;
    my $var_key1 = 'dfhg3df54hg65hg4';
    my $var_key2 = 'dfhg654d6f5g4h5f';
    my $eval = defined $m->getAttribute('eval') && $m->getAttribute('eval') eq 'on';
    if ($eval) {
      # replace variables
      foreach my $variable (@variables) {
        my $replacement = $var_key1.$variable.$var_key2;
        $text =~ s/\$$variable(?![a-zA-Z])/$replacement/ge;
        $text =~ s/\$\{$variable\}/$replacement/ge;
      }
    }
    # check if the expression is enclosed in math separators: $ $$ \( \) \[ \]
    # if so, replace the whole node by dtm or tm
    my $new_text;
    my $new_node_name;
    if ($text =~ /^\s*\$\$([^\$]*)\$\$\s*$/) {
      $new_node_name = 'dtm';
      $new_text = $1;
    } elsif ($text =~ /^\s*\\\[(.*)\\\]\s*$/s) {
      $new_node_name = 'dtm';
      $new_text = $1;
    } elsif ($text =~ /^\s*\$([^\$]*)\$\s*$/) {
      $new_node_name = 'tm';
      $new_text = $1;
    } elsif ($text =~ /^\s*\\\((.*)\\\)\s*$/s) {
      $new_node_name = 'tm';
      $new_text = $1;
    }
    if (defined $new_node_name) {
      if ($eval) {
        foreach my $variable (@variables) {
          my $replacement = $var_key1.$variable.$var_key2;
          $new_text =~ s/$replacement([a-zA-Z])/\${$variable}$1/g;
          $new_text =~ s/$replacement/\$$variable/g;
        }
      }
      my $new_node = $doc->createElement($new_node_name);
      if ($eval) {
        $new_node->setAttribute('eval', 'on');
      }
      $new_node->appendChild($doc->createTextNode($new_text));
      $m->parentNode->replaceChild($new_node, $m);
      next;
    }
    if ($text !~ /\$|\\\(|\\\)|\\\[|\\\]/) {
      # there are no math separators inside
      # try to guess if this is meant as math
      my $found_math = 0;
      foreach my $symbol (@latex_math) {
        if (index($text, $symbol) != -1) {
          $found_math = 1;
          last;
        }
      }
      if ($found_math) {
        # interpret the whole text as LaTeX inline math
        my $new_node = $doc->createElement('tm');
        if ($eval) {
          $new_node->setAttribute('eval', 'on');
        }
        $new_node->appendChild($doc->createTextNode($text_before_variable_replacement));
        $m->parentNode->replaceChild($new_node, $m);
        next;
      }
      # no math symbol found, we will convert the text with tth
    }
    
    # there are math separators inside, even after hiding variables, or there was no math symbol
    
    # hide math parts inside before running tth
    my $math_key1 = '#ghjgdh5hg45gf';
    my $math_key2 = '#';
    my @maths = ();
    my @separators = (['$$','$$'], ['\\(','\\)'], ['\\[','\\]'], ['$','$']);
    foreach my $seps (@separators) {
      my $sep1 = $seps->[0];
      my $sep2 = $seps->[1];
      my $pos1 = index($text, $sep1);
      if ($pos1 == -1) {
        next;
      }
      my $pos2 = index($text, $sep2, $pos1+length($sep1));
      while ($pos1 != -1 && $pos2 != -1) {
        my $replace = substr($text, $pos1, $pos2+length($sep2)-$pos1);
        push(@maths, $replace);
        my $by = $math_key1.scalar(@maths).$math_key2;
        $text = substr($text, 0, $pos1).$by.substr($text, $pos2+length($sep2));
        $pos1 = index($text, $sep1);
        if ($pos1 != -1) {
          $pos2 = index($text, $sep2, $pos1+length($sep1));
        }
      }
    }
    # get HTML as text from tth
    my $html_text = tth($text);
    # replace math by replacements
    for (my $i=0; $i < scalar(@maths); $i++) {
      my $math = $maths[$i];
      $math =~ s/&/&/g;
      $math =~ s/</</g;
      $math =~ s/>/>/g;
      if ($math =~ /^\$\$(.*)\$\$$/s) {
        $math = '<dtm>'.$1.'</dtm>';
      } elsif ($math =~ /^\\\[(.*)\\\]$/s) {
        $math = '<dtm>'.$1.'</dtm>';
      } elsif ($math =~ /^\\\((.*)\\\)$/s) {
        $math = '<tm>'.$1.'</tm>';
      } elsif ($math =~ /^\$(.*)\$$/s) {
        $math = '<tm>'.$1.'</tm>';
      }
      my $replace = $math_key1.($i+1).$math_key2;
      $html_text =~ s/$replace/$math/;
    }
    # replace variables if necessary
    if ($eval) {
      foreach my $variable (@variables) {
        my $replacement = $var_key1.$variable.$var_key2;
        $html_text =~ s/$replacement([a-zA-Z])/\${$variable}$1/g;
        $html_text =~ s/$replacement/\$$variable/g;
      }
    }
    my $fragment = html_to_dom($html_text);
    $doc->adoptNode($fragment);
    $m->parentNode->replaceChild($fragment, $m);
    
  }
  return $fix_by_hand;
}

# Returns the HTML equivalent of LaTeX input, using tth
sub tth {
  my ($text) = @_;
  my ($fh, $tmp_path) = tempfile();
  binmode($fh, ':utf8');
  print $fh $text;
  close $fh;
  my $output = `tth -r -w2 -u -y0 < $tmp_path 2>/dev/null`;
  # hopefully the temp file will not be removed before this point (otherwise we should use unlink_on_destroy 0)
  $output =~ s/^\s*|\s*$//;
  $output =~ s/<div class="p"><!----><\/div>/<br\/>/; # why is tth using such ugly markup for \newline ?
  return $output;
}

# transform simple HTML into a DOM fragment (which will need to be adopted by the document)
sub html_to_dom {
  my ($text) = @_;
  $text = '<root>'.$text.'</root>';
  my $textref = html_to_xml::html_to_xml(\$text);
  utf8::upgrade($$textref); # otherwise the XML parser fails when the HTML parser turns   into a character
  my $dom_doc = XML::LibXML->load_xml(string => $textref);
  my $root = $dom_doc->documentElement;
  remove_empty_style($root);
  my $fragment = $dom_doc->createDocumentFragment();
  my $next;
  for (my $n=$root->firstChild; defined $n; $n=$next) {
    $next = $n->nextSibling;
    $root->removeChild($n);
    $fragment->appendChild($n);
  }
  return($fragment);
}

# Use the linked sty files to guess which newly defined elements should be considered blocks.
# Also adds to @containing_styles the sty elements that contain styles.
# @param {string} fn - the .lc file path (we only extract the directory path from it)
sub add_sty_blocks {
  my ($fn, $root, $all_block) = @_;
  my $doc = $root->ownerDocument;
  my @parserlibs = $doc->getElementsByTagName('parserlib');
  my @libs = ();
  foreach my $parserlib (@parserlibs) {
    if (defined $parserlib->firstChild && $parserlib->firstChild->nodeType == XML_TEXT_NODE) {
      my $value = $parserlib->firstChild->nodeValue;
      $value =~ s/^\s+|\s+$//g;
      if ($value ne '') {
        push(@libs, $value);
      }
    }
  }
  my ($name, $path, $suffix) = fileparse($fn);
  foreach my $sty (@libs) {
    if (substr($sty, 0, 1) eq '/') {
      $sty = $RES_DIR.$sty;
    } else {
      $sty = $path.$sty;
    }
    my $new_elements = parse_sty($sty, $all_block);
    better_guess($root, $new_elements, $all_block);
    my $new_blocks = $new_elements->{'block'};
    my $new_inlines = $new_elements->{'inline'};
    push(@$all_block, @{$new_blocks});
    #push(@inlines, @{$new_inlines}); # we are not using a list of inline elements at this point
  }
}

##
# Parses a sty file and returns lists of block and inline elements.
# @param {string} fn - the file path
##
sub parse_sty {
  my ($fn, $all_block) = @_;
  my @blocks = ();
  my @inlines = ();
  my $p = HTML::TokeParser->new($fn);
  if (! $p) {
    die "post_xml.pl: parse_sty: Error reading $fn\n";
  }
  $p->empty_element_tags(1);
  my $in_definetag = 0;
  my $in_render = 0;
  my %newtags = ();
  my $newtag = '';
  my $is_block = 0;
  while (my $token = $p->get_token) {
    if ($token->[0] eq 'S') {
      my $tag = lc($token->[1]);
      if ($tag eq 'definetag') {
        $in_definetag = 1;
        $is_block = 0;
        my $attributes = $token->[2];
        $newtag = $attributes->{'name'};
        if (substr($newtag, 0, 1) eq '/') {
          $newtag = substr($newtag, 1);
        }
      } elsif ($in_definetag && $tag eq 'render') {
        $in_render = 1;
        $is_block = 0;
      } elsif ($in_render) {
        if (string_in_array($all_block, $tag)) {
          $is_block = 1;
        }
      }
    } elsif ($token->[0] eq 'E') {
      my $tag = lc($token->[1]);
      if ($tag eq 'definetag') {
        $in_definetag = 0;
        if (defined $newtags{$newtag}) {
          $newtags{$newtag} = $newtags{$newtag} || $is_block;
        } else {
          $newtags{$newtag} = $is_block;
        }
      } elsif ($in_definetag && $tag eq 'render') {
        $in_render = 0;
      }
    }
  }
  foreach $newtag (keys(%newtags)) {
    if ($newtags{$newtag} == 1) {
      push(@blocks, $newtag);
    } else {
      push(@inlines, $newtag);
    }
  }
  return {'block'=>\@blocks, 'inline'=>\@inlines};
}

##
# Marks as block the elements that contain block elements in the input file.
# Also adds to @containing_styles the sty elements that contain styles.
# @param {string} fn - the file path
# @param {Hash<string,Array>} new_elements - contains arrays in 'block' and 'inline'
##
sub better_guess {
  my ($root, $new_elements, $all_block) = @_;
  my $new_blocks = $new_elements->{'block'};
  my $new_inlines = $new_elements->{'inline'};
  
  my @change = (); # change these elements from inline to block
  foreach my $tag (@{$new_inlines}) {
    my @nodes = $root->getElementsByTagName($tag);
    NODE_LOOP: foreach my $node (@nodes) {
      for (my $child=$node->firstChild; defined $child; $child=$child->nextSibling) {
        if ($child->nodeType == XML_ELEMENT_NODE) {
          if (string_in_array($all_block, $child->nodeName) || string_in_array($new_blocks, $child->nodeName)) {
            push(@change, $tag);
            last NODE_LOOP;
          }
        }
      }
    }
  }
  foreach my $inline (@change) {
    my $index = 0;
    $index++ until $new_inlines->[$index] eq $inline;
    splice(@{$new_inlines}, $index, 1);
    push(@{$new_blocks}, $inline);
  }
  # add to @containing_styles when a style is used inside
  # NOTE: some sty elements will be added even though they should not, but if we don't do that
  # all style will be removed in the sty elements.
  foreach my $tag ((@{$new_blocks}, @{$new_inlines})) {
    my @nodes = $root->getElementsByTagName($tag);
    NODE_LOOP: foreach my $node (@nodes) {
      for (my $child=$node->firstChild; defined $child; $child=$child->nextSibling) {
        if ($child->nodeType == XML_ELEMENT_NODE) {
          if (string_in_array(\@html_styles, $child->nodeName)) {
            push(@containing_styles, $tag);
            last NODE_LOOP;
          }
        }
      }
    }
  }
}

# When a style element contains a block, move the style inside the block where it is allowed.
# style/block/other -> block/style/other
# When a style is used where it is not allowed, move it inside its children or remove it (unless it contains only text)
# element_not_containing_styles/style/other -> element_not_containing_styles/other/style (except if other is a style)
# The fix is not perfect in the case of element_not_containing_styles/style1/style2/block/text (style1 will be lost):
# element_not_containing_styles/style1/style2/block/text -> element_not_containing_styles/block/style2/text
# (a solution to this problem would be to merge the styles in a span)
# NOTE: .sty defined elements might have been added to @containing_styles by better_guess().
sub fix_block_styles {
  my ($element, $all_block) = @_;
  my $doc = $element->ownerDocument;
  if (string_in_array(\@html_styles, $element->nodeName)) {
    # move spaces out of the style element
    if (defined $element->firstChild && $element->firstChild->nodeType == XML_TEXT_NODE) {
      my $child = $element->firstChild;
      if ($child->nodeValue =~ /^(\s+)(\S.*)$/s) {
        $element->parentNode->insertBefore($doc->createTextNode($1), $element);
        $child->setData($2);
      }
    }
    if (defined $element->lastChild && $element->lastChild->nodeType == XML_TEXT_NODE) {
      my $child = $element->lastChild;
      if ($child->nodeValue =~ /^(.*\S)(\s+)$/s) {
        $element->parentNode->insertAfter($doc->createTextNode($2), $element);
        $child->setData($1);
      }
    }
    
    my $found_block = 0;
    for (my $child=$element->firstChild; defined $child; $child=$child->nextSibling) {
      if ($child->nodeType == XML_ELEMENT_NODE && string_in_array($all_block, $child->nodeName)) {
        $found_block = 1;
        last;
      }
    }
    my $no_style_here = !string_in_array(\@containing_styles, $element->parentNode->nodeName);
    if ($found_block || $no_style_here) {
      # there is a block or the style is not allowed here,
      # the style element has to be replaced by its modified children
      my $s; # a clone of the style
      my $next;
      for (my $child=$element->firstChild; defined $child; $child=$next) {
        $next = $child->nextSibling;
        if ($child->nodeType == XML_ELEMENT_NODE && (string_in_array($all_block, $child->nodeName) ||
            $child->nodeName eq 'br' || $no_style_here)) {
          # avoid inverting a style with a style with $no_style_here (that would cause endless recursion)
          if (!$no_style_here || (!string_in_array(\@html_styles, $child->nodeName) &&
              string_in_array(\@containing_styles, $child->nodeName))) {
            # block node or inline node when the style is not allowed:
            # move all children inside the style, and make the style the only child
            $s = $element->cloneNode();
            my $next2;
            for (my $child2=$child->firstChild; defined $child2; $child2=$next2) {
              $next2 = $child2->nextSibling;
              $child->removeChild($child2);
              $s->appendChild($child2);
            }
            $child->appendChild($s);
          }
          $s = undef;
        } elsif (($child->nodeType == XML_TEXT_NODE && $child->nodeValue !~ /^\s*$/) ||
            $child->nodeType == XML_ELEMENT_NODE) {
          # if the style is allowed, move text and inline nodes inside the style
          if (!$no_style_here) {
            if (!defined $s) {
              $s = $element->cloneNode();
              $element->insertBefore($s, $child);
            }
            $element->removeChild($child);
            $s->appendChild($child);
          }
        } else {
          # do not put other nodes inside the style
          $s = undef;
        }
      }
      # now replace by children and fix them
      my $parent = $element->parentNode;
      for (my $child=$element->firstChild; defined $child; $child=$next) {
        $next = $child->nextSibling;
        $element->removeChild($child);
        $parent->insertBefore($child, $element);
        if ($child->nodeType == XML_ELEMENT_NODE) {
          fix_block_styles($child, $all_block);
        }
      }
      $parent->removeChild($element);
      return;
    }
  }
  # otherwise fix all children
  my $next;
  for (my $child=$element->firstChild; defined $child; $child=$next) {
    $next = $child->nextSibling;
    if ($child->nodeType == XML_ELEMENT_NODE) {
      fix_block_styles($child, $all_block);
    }
  }
}

# removes empty font elements and font elements that contain at least one block element
# replaces other font elements by equivalent span
sub fix_fonts {
  my ($root, $all_block) = @_;
  my $doc = $root->ownerDocument;
  my @fonts = $root->getElementsByTagName('font');
  @fonts = reverse(@fonts); # to deal with the ancestor last in the case of font/font
  foreach my $font (@fonts) {
    my $block = 0;
    for (my $child=$font->firstChild; defined $child; $child=$child->nextSibling) {
      if (string_in_array($all_block, $child->nodeName) || string_in_array(\@inline_like_block, $child->nodeName)) {
        $block = 1;
        last;
      }
    }
    if (!defined $font->firstChild || $block) {
      # empty font or font containing block elements
      # replace this node by its content
      replace_by_children($font);
    } else {
      # replace by equivalent span
      my $color = get_non_empty_attribute($font, 'color');
      my $size = get_non_empty_attribute($font, 'size');
      my $face = get_non_empty_attribute($font, 'face');
      if (defined $face) {
        $face =~ s/^,|,$//;
      }
      if (!defined $color && !defined $size && !defined $face) {
        # useless font element: replace this node by its content
        replace_by_children($font);
        next;
      }
      my $replacement;
      tie (my %properties, 'Tie::IxHash', ());
      if (!defined $color && !defined $size && defined $face && lc($face) eq 'symbol') {
        $replacement = $doc->createDocumentFragment();
      } else {
        $replacement = $doc->createElement('span');
        my $css = '';
        if (defined $color) {
          $color =~ s/^x//;
          $properties{'color'} = $color;
        }
        if (defined $size) {
          my %hash = (
            '1' => 'x-small',
            '2' => 'small',
            '3' => 'medium',
            '4' => 'large',
            '5' => 'x-large',
            '6' => 'xx-large',
            '7' => '300%',
            '-1' => 'small',
            '-2' => 'x-small',
            '+1' => 'large',
            '+2' => 'x-large',
            '+3' => 'xx-large',
            '+4' => '300%',
          );
          my $value = $hash{$size};
          if (!defined $value) {
            $value = 'medium';
          }
          $properties{'font-size'} = $value;
        }
        if (defined $face) {
          if (lc($face) ne 'symbol' && lc($face) ne 'bold') {
            $properties{'font-family'} = $face;
          }
        }
        set_css_properties($replacement, \%properties);
      }
      if (defined $face && lc($face) eq 'symbol') {
        # convert all content to unicode
        my $next;
        for (my $child=$font->firstChild; defined $child; $child=$next) {
          $next = $child->nextSibling;
          if ($child->nodeType == XML_TEXT_NODE) {
            my $value = $child->nodeValue;
            $value =~ tr/ABGDEZHQIKLMNXOPRSTUFCYWabgdezhqiklmnxoprVstufcywJjv¡«¬®/ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩαβγδεζηθικλμνξοπρςστυφχψωϑϕϖϒ↔←→/;
            $child->setData($value);
          }
        }
      }
      # replace the font node
      if ($replacement->nodeType == XML_ELEMENT_NODE && !defined $font->previousSibling &&
          !defined $font->nextSibling && string_in_array(\@accepting_style, $font->parentNode->nodeName)) {
        # use CSS on the parent block and replace font by its children instead of using a new element
        set_css_properties($font->parentNode, \%properties);
        replace_by_children($font);
      } else {
        # move all font children inside the replacement (span or fragment)
        my $next;
        for (my $child=$font->firstChild; defined $child; $child=$next) {
          $next = $child->nextSibling;
          $font->removeChild($child);
          $replacement->appendChild($child);
        }
        # replace font
        $font->parentNode->replaceChild($replacement, $font);
      }
    }
  }
  $root->normalize();
}

# replaces u by <span style="text-decoration: underline">
sub replace_u {
  my ($root) = @_;
  my $doc = $root->ownerDocument;
  my @us = $root->getElementsByTagName('u');
  foreach my $u (@us) {
    my $span = $doc->createElement('span');
    $span->setAttribute('style', 'text-decoration: underline');
    my $next;
    for (my $child=$u->firstChild; defined $child; $child=$next) {
      $next = $child->nextSibling;
      $u->removeChild($child);
      $span->appendChild($child);
    }
    $u->parentNode->replaceChild($span, $u);
  }
}

# removes CDATA sections tags that have not been parsed correcty by the HTML parser
# also removes bad comments in script elements
sub remove_bad_cdata_sections {
  my ($root) = @_;
  my $doc = $root->ownerDocument;
  foreach my $name (@preserve_elements) {
    my @nodes = $root->getElementsByTagName($name);
    foreach my $node (@nodes) {
      if (defined $node->firstChild && $node->firstChild->nodeType == XML_TEXT_NODE) {
        my $value = $node->firstChild->nodeValue;
        if ($name eq 'script' && (!defined $node->getAttribute('type') || $node->getAttribute('type') ne 'loncapa/perl') &&
            !defined $node->firstChild->nextSibling && $value =~ /^(\s*)<!--(.*)-->(\s*)$/) {
          # web browsers interpret that as a real comment when it is on 1 line, but the Perl HTML parser thinks it is the script
          # -> turning it back into a comment
          # (this is only true for Javascript script elements, since LON-CAPA does not parse loncapa/perl scripts in the same way)
          $node->removeChild($node->firstChild);
          $node->appendChild($doc->createComment($2));
          next;
        }
        # at the beginning:
        $value =~ s/^(\s*)<!\[CDATA\[/$1/; # <![CDATA[
        $value =~ s/^(\s*)\/\*\s*<!\[CDATA\[\s*\*\//$1/; # /* <![CDATA[ */
        $value =~ s/^(\s*)\/\/\s*<!\[CDATA\[/$1/; # // <![CDATA[
        $value =~ s/^(\s*)(\/\/)?\s*<!--/$1/; # // <!--
        # at the end:
        $value =~ s/\/\/\s*\]\]>(\s*)$/$1/; # // ]]>
        $value =~ s/\]\]>(\s*)$/$1/; # ]]>
        $value =~ s/(\/\/)?\s*-->(\s*)$/$2/; # // -->
        $value =~ s/\/\*\s*\]\]>\s*\*\/(\s*)$/$1/; # /* ]]> */
        
        $value = "\n".$value."\n";
        $value =~ s/\s*(\n[ \t]*)/$1/;
        $value =~ s/\s+$/\n/;
        $node->firstChild->setData($value);
      }
    }
  }
}

# adds CDATA sections to scripts
sub add_cdata_sections {
  my ($root) = @_;
  my $doc = $root->ownerDocument;
  my @scripts = $root->getElementsByTagName('script');
  my @answers = $root->getElementsByTagName('answer');
  foreach my $answer (@answers) {
    my $ancestor = $answer->parentNode;
    my $found_capa_response = 0;
    while (defined $ancestor) {
      if ($ancestor->nodeName eq 'numericalresponse' || $ancestor->nodeName eq 'formularesponse') {
        $found_capa_response = 1;
        last;
      }
      $ancestor = $ancestor->parentNode;
    }
    if (!$found_capa_response) {
      push(@scripts, $answer);
    }
  }
  foreach my $script (@scripts) {
    # use a CDATA section in the normal situation, for any script
    my $first = $script->firstChild;
    if (defined $first && $first->nodeType == XML_TEXT_NODE && !defined $first->nextSibling) {
      my $cdata = $doc->createCDATASection($first->nodeValue);
      $script->replaceChild($cdata, $first);
    }
  }
}

# removes "<!--" and "-->" at the beginning and end of style elements
sub fix_style_element {
  my ($root) = @_;
  my @styles = $root->getElementsByTagName('style');
  foreach my $style (@styles) {
    if (defined $style->firstChild && $style->firstChild->nodeType == XML_TEXT_NODE &&
        !defined $style->firstChild->nextSibling) {
      my $text = $style->firstChild->nodeValue;
      if ($text =~ /^\s*<!--(.*)-->\s*$/s) {
        $style->firstChild->setData($1);
      }
    }
  }
}

# create missing cells at the end of table rows
sub fix_tables {
  my ($root) = @_;
  my @tables = $root->getElementsByTagName('table');
  foreach my $table (@tables) {
    fix_cells($table);
    foreach my $tbody ($table->getChildrenByTagName('tbody')) {
      fix_cells($tbody);
    }
    foreach my $thead ($table->getChildrenByTagName('thead')) {
      fix_cells($thead);
    }
    foreach my $tfoot ($table->getChildrenByTagName('tfoot')) {
      fix_cells($tfoot);
    }
  }
}

# create missing cells at the end of table rows
sub fix_cells {
  my ($table) = @_; # could actually be table, tbody, thead or tfoot
  my $doc = $table->ownerDocument;
  my @nb_cells = ();
  my $max_nb_cells = 0;
  my @rowspans = ();
  my @trs = $table->getChildrenByTagName('tr');
  foreach my $tr (@trs) {
    my $nb_cells;
    if (defined $rowspans[0]) {
      $nb_cells = shift(@rowspans);
    } else {
      $nb_cells = 0;
    }
    for (my $cell=$tr->firstChild; defined $cell; $cell=$cell->nextSibling) {
      if ($cell->nodeName eq 'td' || $cell->nodeName eq 'th') {
        my $colspan = $cell->getAttribute('colspan');
        if (defined $colspan && $colspan =~ /^\s*[0-9]+\s*$/) {
          $nb_cells += $colspan;
        } else {
          $nb_cells++;
        }
        my $rowspan = $cell->getAttribute('rowspan');
        if (defined $rowspan && $rowspan =~ /^\s*[0-9]+\s*$/) {
          for (my $i=0; $i < $rowspan-1; $i++) {
            if (!defined $rowspans[$i]) {
              $rowspans[$i] = 1;
            } else {
              $rowspans[$i]++;
            }
          }
        }
      }
    }
    push(@nb_cells, $nb_cells);
    if ($nb_cells > $max_nb_cells) {
      $max_nb_cells = $nb_cells;
    }
  }
  foreach my $tr (@trs) {
    my $nb_cells = shift(@nb_cells);
    if ($nb_cells < $max_nb_cells) {
      for (1..($max_nb_cells - $nb_cells)) {
        $tr->appendChild($doc->createElement('td'));
      }
    }
  }
}

# replaces ul/ul by ul/li/ul and the same for ol (using the previous li if possible)
# also adds a ul element when a li has no ul/ol ancestor
sub fix_lists {
  my ($root) = @_;
  my $doc = $root->ownerDocument;
  my @uls = $root->getElementsByTagName('ul');
  my @ols = $root->getElementsByTagName('ol');
  my @lists = (@uls, @ols);
  foreach my $list (@lists) {
    my $next;
    for (my $child=$list->firstChild; defined $child; $child=$next) {
      $next = $child->nextSibling;
      if ($child->nodeType == XML_ELEMENT_NODE && string_in_array(['ul','ol'], $child->nodeName)) {
        my $previous = $child->previousNonBlankSibling(); # note: non-DOM method
        $list->removeChild($child);
        if (defined $previous && $previous->nodeType == XML_ELEMENT_NODE && $previous->nodeName eq 'li') {
          $previous->appendChild($child);
        } else {
          my $li = $doc->createElement('li');
          $li->appendChild($child);
          if (!defined $next) {
            $list->appendChild($li);
          } else {
            $list->insertBefore($li, $next);
          }
        }
      }
    }
  }
  my @lis = $root->getElementsByTagName('li');
  foreach my $li (@lis) {
    my $found_list_ancestor = 0;
    my $ancestor = $li->parentNode;
    while (defined $ancestor) {
      if ($ancestor->nodeName eq 'ul' || $ancestor->nodeName eq 'ol') {
        $found_list_ancestor = 1;
        last;
      }
      $ancestor = $ancestor->parentNode;
    }
    if (!$found_list_ancestor) {
      # replace li by ul and add li under ul
      my $ul = $doc->createElement('ul');
      $li->parentNode->insertBefore($ul, $li);
      $li->parentNode->removeChild($li);
      $ul->appendChild($li);
      # add all other li afterwards inside ul (there might be text nodes in-between)
      my $next = $ul->nextSibling;
      while (defined $next) {
        my $next_next = $next->nextSibling;
        if ($next->nodeType == XML_TEXT_NODE && $next->nodeValue =~ /^\s*$/ &&
            defined $next_next && $next_next->nodeType == XML_ELEMENT_NODE && $next_next->nodeName eq 'li') {
          $next->parentNode->removeChild($next);
          $ul->appendChild($next);
          $next = $next_next;
          $next_next = $next_next->nextSibling;
        }
        if ($next->nodeType == XML_ELEMENT_NODE && $next->nodeName eq 'li') {
          $next->parentNode->removeChild($next);
          $ul->appendChild($next);
        } else {
          last;
        }
        $next = $next_next;
      }
    }
  }
}

# Some "image" elements are actually img element with a wrong name. This renames them.
# Amazingly enough, "<image src=..." displays an image in some browsers
# ("image" has existed at some point as an experimental HTML element).
sub fix_wrong_name_for_img {
  my ($root) = @_;
  my @images = $root->getElementsByTagName('image');
  foreach my $image (@images) {
    if (!defined $image->getAttribute('src')) {
      next;
    }
    my $found_correct_ancestor = 0;
    my $ancestor = $image->parentNode;
    while (defined $ancestor) {
      if ($ancestor->nodeName eq 'drawimage' || $ancestor->nodeName eq 'imageresponse') {
        $found_correct_ancestor = 1;
        last;
      }
      $ancestor = $ancestor->parentNode;
    }
    if ($found_correct_ancestor) {
      next;
    }
    # this really has to be renamed "img"
    $image->setNodeName('img');
  }
}

# Replaces many deprecated attributes and replaces them by equivalent CSS when possible
sub replace_deprecated_attributes_by_css {
  my ($root) = @_;
  
  fix_deprecated_in_tables($root);
  
  fix_deprecated_in_table_rows($root);
  
  fix_deprecated_in_table_cells($root);
  
  fix_deprecated_in_lists($root);
  
  fix_deprecated_in_list_items($root);
  
  fix_deprecated_in_hr($root);
  
  fix_deprecated_in_img($root);
  
  fix_deprecated_in_body($root);
  
  fix_align_attribute($root);
}

# Replaces deprecated attributes in tables
sub fix_deprecated_in_tables {
  my ($root) = @_;
  my @tables = $root->getElementsByTagName('table');
  foreach my $table (@tables) {
    tie (my %new_properties, 'Tie::IxHash', ());
    my $align = $table->getAttribute('align');
    if (defined $align) {
      $table->removeAttribute('align');
      $align = lc(trim($align));
    }
    if ($table->parentNode->nodeName eq 'center' || (defined $align && $align eq 'center') ||
        (defined $table->parentNode->getAttribute('align') && $table->parentNode->getAttribute('align') eq 'center')) {
      $new_properties{'margin-left'} = 'auto';
      $new_properties{'margin-right'} = 'auto';
    }
    if (defined $align && ($align eq 'left' || $align eq 'right')) {
      $new_properties{'float'} = $align;
    }
    my $width = $table->getAttribute('width');
    if (defined $width) {
      $table->removeAttribute('width');
      $width = trim($width);
      if ($width =~ /^[0-9]+$/) {
        $width .= 'px';
      }
      if ($width ne '') {
        $new_properties{'width'} = $width;
      }
    }
    my $height = $table->getAttribute('height');
    if (defined $height) {
      $table->removeAttribute('height');
      # no replacement for table height
    }
    my $bgcolor = $table->getAttribute('bgcolor');
    if (defined $bgcolor) {
      $table->removeAttribute('bgcolor');
      $bgcolor = trim($bgcolor);
      $bgcolor =~ s/^x\s*//;
      if ($bgcolor ne '') {
        $new_properties{'background-color'} = $bgcolor;
      }
    }
    my $frame = $table->getAttribute('frame');
    if (defined $frame) {
      $table->removeAttribute('frame');
      $frame = lc(trim($frame));
      if ($frame eq 'void') {
        $new_properties{'border'} = 'none';
      } elsif ($frame eq 'above') {
        $new_properties{'border-top'} = '1px solid black';
      } elsif ($frame eq 'below') {
        $new_properties{'border-bottom'} = '1px solid black';
      } elsif ($frame eq 'hsides') {
        $new_properties{'border-top'} = '1px solid black';
        $new_properties{'border-bottom'} = '1px solid black';
      } elsif ($frame eq 'vsides') {
        $new_properties{'border-left'} = '1px solid black';
        $new_properties{'border-right'} = '1px solid black';
      } elsif ($frame eq 'lhs') {
        $new_properties{'border-left'} = '1px solid black';
      } elsif ($frame eq 'rhs') {
        $new_properties{'border-right'} = '1px solid black';
      } elsif ($frame eq 'box') {
        $new_properties{'border'} = '1px solid black';
      } elsif ($frame eq 'border') {
        $new_properties{'border'} = '1px solid black';
      }
    }
    if (scalar(keys %new_properties) > 0) {
      set_css_properties($table, \%new_properties);
    }
    # we can't replace the border attribute without creating a style block, but we can improve things like border="BORDER"
    my $border = $table->getAttribute('border');
    if (defined $border) {
      $border = trim($border);
      if ($border !~ /^\s*[0-9]+\s*(px)?\s*$/) {
        $table->setAttribute('border', '1');
      }
    }
  }
  
}

# Replaces deprecated attributes in tr elements
sub fix_deprecated_in_table_rows {
  my ($root) = @_;
  my @trs = $root->getElementsByTagName('tr');
  foreach my $tr (@trs) {
    my $old_properties = get_css_properties($tr);
    tie (my %new_properties, 'Tie::IxHash', ());
    my $bgcolor = $tr->getAttribute('bgcolor');
    if (defined $bgcolor) {
      $tr->removeAttribute('bgcolor');
      if (!defined $old_properties->{'background-color'}) {
        $bgcolor = trim($bgcolor);
        $bgcolor =~ s/^x\s*//;
        if ($bgcolor ne '') {
          $new_properties{'background-color'} = $bgcolor;
        }
      }
    }
    my $align = $tr->getAttribute('align');
    if (defined $align && $align !~ /\s*char\s*/i) {
      $tr->removeAttribute('align');
      if (!defined $old_properties->{'text-align'}) {
        $align = lc(trim($align));
        if ($align ne '') {
          $new_properties{'text-align'} = $align;
        }
      }
    }
    my $valign = $tr->getAttribute('valign');
    if (defined $valign) {
      $tr->removeAttribute('valign');
      if (!defined $old_properties->{'vertical-align'}) {
        $valign = lc(trim($valign));
        if ($valign ne '') {
          $new_properties{'vertical-align'} = $valign;
        }
      }
    }
    if (scalar(keys %new_properties) > 0) {
      set_css_properties($tr, \%new_properties);
    }
  }
}

# Replaces deprecated attributes in table cells (td and th)
sub fix_deprecated_in_table_cells {
  my ($root) = @_;
  my @tds = $root->getElementsByTagName('td');
  my @ths = $root->getElementsByTagName('th');
  my @cells = (@tds, @ths);
  foreach my $cell (@cells) {
    my $old_properties = get_css_properties($cell);
    tie (my %new_properties, 'Tie::IxHash', ());
    my $width = $cell->getAttribute('width');
    if (defined $width) {
      $cell->removeAttribute('width');
      if (!defined $old_properties->{'width'}) {
        $width = trim($width);
        if ($width =~ /^[0-9]+$/) {
          $width .= 'px';
        }
        if ($width ne '') {
          $new_properties{'width'} = $width;
        }
      }
    }
    my $height = $cell->getAttribute('height');
    if (defined $height) {
      $cell->removeAttribute('height');
      if (!defined $old_properties->{'height'}) {
        $height = trim($height);
        if ($height =~ /^[0-9]+$/) {
          $height .= 'px';
        }
        if ($height ne '') {
          $new_properties{'height'} = $height;
        }
      }
    }
    my $bgcolor = $cell->getAttribute('bgcolor');
    if (defined $bgcolor) {
      $cell->removeAttribute('bgcolor');
      if (!defined $old_properties->{'background-color'}) {
        $bgcolor = trim($bgcolor);
        $bgcolor =~ s/^x\s*//;
        if ($bgcolor ne '') {
          $new_properties{'background-color'} = $bgcolor;
        }
      }
    }
    my $align = $cell->getAttribute('align');
    if (defined $align && $align !~ /\s*char\s*/i) {
      $cell->removeAttribute('align');
      if (!defined $old_properties->{'text-align'}) {
        $align = lc(trim($align));
        if ($align ne '') {
          $new_properties{'text-align'} = $align;
        }
      }
    }
    my $valign = $cell->getAttribute('valign');
    if (defined $valign) {
      $cell->removeAttribute('valign');
      if (!defined $old_properties->{'vertical-align'}) {
        $valign = lc(trim($valign));
        if ($valign ne '') {
          $new_properties{'vertical-align'} = $valign;
        }
      }
    }
    if (scalar(keys %new_properties) > 0) {
      set_css_properties($cell, \%new_properties);
    }
  }
}

# Replaces deprecated attributes in lists (ul and ol)
sub fix_deprecated_in_lists {
  my ($root) = @_;
  my @uls = $root->getElementsByTagName('ul');
  my @ols = $root->getElementsByTagName('ol');
  my @lists = (@uls, @ols);
  foreach my $list (@lists) {
    my $type = $list->getAttribute('type');
    if (defined $type) {
      my $lst = list_style_type($type);
      if (defined $lst) {
        $list->removeAttribute('type');
        if (!defined get_css_property($list, 'list-style-type')) {
          set_css_property($list, 'list-style-type', $lst);
        }
      }
    }
  }
}

# Replaces deprecated attributes in list items (li)
sub fix_deprecated_in_list_items {
  my ($root) = @_;
  my @lis = $root->getElementsByTagName('li');
  foreach my $li (@lis) {
    my $type = $li->getAttribute('type');
    if (defined $type) {
      my $lst = list_style_type($type);
      if (defined $lst) {
        $li->removeAttribute('type');
        if (!defined get_css_property($li, 'list-style-type')) {
          set_css_property($li, 'list-style-type', $lst);
        }
      }
    }
  }
}

# returns the CSS list-style-type value equivalent to the given type attribute for a list or list item
sub list_style_type {
  my ($type) = @_;
  my $value;
  $type = trim($type);
  if (lc($type) eq 'circle') {
    $value = 'circle';
  } elsif (lc($type) eq 'disc') {
    $value = 'disc';
  } elsif (lc($type) eq 'square') {
    $value = 'square';
  } elsif ($type eq 'a') {
    $value = 'lower-latin';
  } elsif ($type eq 'A') {
    $value = 'upper-latin';
  } elsif ($type eq 'i') {
    $value = 'lower-roman';
  } elsif ($type eq 'I') {
    $value = 'upper-roman';
  } elsif ($type eq '1') {
    $value = 'decimal';
  }
  return $value;
}

# Replaces deprecated attributes in hr
sub fix_deprecated_in_hr {
  my ($root) = @_;
  my @hrs = $root->getElementsByTagName('hr');
  foreach my $hr (@hrs) {
    tie (my %new_properties, 'Tie::IxHash', ());
    my $align = $hr->getAttribute('align');
    if (defined $align) {
      $align = lc(trim($align));
      if ($align eq 'left') {
        $new_properties{'text-align'} = 'left';
        $new_properties{'margin-left'} = '0';
      } elsif ($align eq 'right') {
        $new_properties{'text-align'} = 'right';
        $new_properties{'margin-right'} = '0';
      }
      $hr->removeAttribute('align');
    }
    my $color = $hr->getAttribute('color');
    if (defined $color) {
      $color = trim($color);
      $color =~ s/^x\s*//;
      if ($color ne '') {
        $new_properties{'color'} = $color;
        $new_properties{'background-color'} = $color;
      }
      $hr->removeAttribute('color');
    }
    my $noshade = $hr->getAttribute('noshade');
    my $size = $hr->getAttribute('size');
    if (defined $noshade) {
      $new_properties{'border-width'} = '0';
      if (!defined $color) {
        $new_properties{'color'} = 'gray';
        $new_properties{'background-color'} = 'gray';
      }
      if (!defined $size) {
        $size = '2';
      }
      $hr->removeAttribute('noshade');
    }
    if (defined $size) {
      $size = trim($size);
      if ($size ne '') {
        $new_properties{'height'} = $size.'px';
      }
      if (defined $hr->getAttribute('size')) {
        $hr->removeAttribute('size');
      }
    }
    my $width = $hr->getAttribute('width');
    if (defined $width) {
      $width = trim($width);
      if ($width ne '') {
        if ($width !~ /\%$/) {
          $width .= 'px';
        }
        $new_properties{'width'} = $width;
      }
      $hr->removeAttribute('width');
    }
    if (scalar(keys %new_properties) > 0) {
      set_css_properties($hr, \%new_properties);
    }
  }
}

# Replaces deprecated attributes in img
sub fix_deprecated_in_img {
  my ($root) = @_;
  my @imgs = $root->getElementsByTagName('img');
  foreach my $img (@imgs) {
    my $old_properties = get_css_properties($img);
    tie (my %new_properties, 'Tie::IxHash', ());
    my $align = $img->getAttribute('align');
    if (defined $align) {
      $align = lc(trim($align));
      if ($align eq 'middle' || $align eq 'top' || $align eq 'bottom') {
        $img->removeAttribute('align');
        if (!defined $old_properties->{'vertical-align'}) {
          $new_properties{'vertical-align'} = $align;
        }
      } elsif ($align eq 'left' || $align eq 'right') {
        $img->removeAttribute('align');
        if (!defined $old_properties->{'float'}) {
          $new_properties{'float'} = $align;
        }
      } elsif ($align eq 'center' || $align eq '') {
        $img->removeAttribute('align');
      }
    }
    my $border = $img->getAttribute('border');
    if (defined $border) {
      $border = lc(trim($border));
      if ($border =~ /^[0-9]+\s*(px)?$/) {
        $img->removeAttribute('border');
        if (!defined $old_properties->{'border'}) {
          if ($border !~ /px$/) {
            $border .= 'px';
          }
          $new_properties{'border'} = $border.' solid black';
        }
      }
    }
    my $hspace = $img->getAttribute('hspace');
    if (defined $hspace) {
      $hspace = lc(trim($hspace));
      if ($hspace =~ /^[0-9]+\s*(px)?$/) {
        $img->removeAttribute('hspace');
        if (!defined $old_properties->{'margin-left'} || !defined $old_properties->{'margin-right'}) {
          if ($hspace !~ /px$/) {
            $hspace .= 'px';
          }
          $new_properties{'margin-left'} = $hspace;
          $new_properties{'margin-right'} = $hspace;
        }
      }
    }
    if (scalar(keys %new_properties) > 0) {
      set_css_properties($img, \%new_properties);
    }
  }
}

# Replaces deprecated attributes in htmlbody (the style attribute could be used in a div for output)
sub fix_deprecated_in_body {
  my ($root) = @_;
  my $doc = $root->ownerDocument;
  my @bodies = $root->getElementsByTagName('htmlbody');
  foreach my $body (@bodies) {
    my $old_properties = get_css_properties($body);
    tie (my %new_properties, 'Tie::IxHash', ());
    my $bgcolor = $body->getAttribute('bgcolor');
    if (defined $bgcolor) {
      $body->removeAttribute('bgcolor');
      if (!defined $old_properties->{'background-color'}) {
        $bgcolor = trim($bgcolor);
        $bgcolor =~ s/^x\s*//;
        if ($bgcolor ne '') {
          $new_properties{'background-color'} = $bgcolor;
        }
      }
    }
    my $color = $body->getAttribute('text');
    if (defined $color) {
      $body->removeAttribute('text');
      if (!defined $old_properties->{'color'}) {
        $color = trim($color);
        $color =~ s/^x\s*//;
        if ($color ne '') {
          $new_properties{'color'} = $color;
        }
      }
    }
    my $background = $body->getAttribute('background');
    if (defined $background && ($background =~ /\.jpe?g$|\.gif|\.png/i)) {
      $body->removeAttribute('background');
      if (!defined $old_properties->{'background-image'}) {
        $background = trim($background);
        if ($background ne '') {
          $new_properties{'background-image'} = 'url('.$background.')';
        }
      }
    }
    # NOTE: these attributes have never been standard and are better removed with no replacement
    foreach my $bad ('bottommargin', 'leftmargin', 'rightmargin', 'topmargin', 'marginheight', 'marginwidth') {
      if ($body->hasAttribute($bad)) {
        $body->removeAttribute($bad);
      }
    }
    # NOTE: link alink and vlink require a <style> block to be converted
    my $link = $body->getAttribute('link');
    my $alink = $body->getAttribute('alink');
    my $vlink = $body->getAttribute('vlink');
    if (defined $link || defined $alink || defined $vlink) {
      my $head;
      my @heads = $root->getElementsByTagName('htmlhead');
      if (scalar(@heads) > 0) {
        $head = $heads[0];
      } else {
        $head = $doc->createElement('htmlhead');
        $root->insertBefore($head, $root->firstChild);
      }
      my $style = $doc->createElement('style');
      $head->appendChild($style);
      my $css = "\n";
      if (defined $link) {
        $body->removeAttribute('link');
        $link = trim($link);
        $link =~ s/^x\s*//;
        $css .= '      a:link { color:'.$link.' }';
        $css .= "\n";
      }
      if (defined $alink) {
        $body->removeAttribute('alink');
        $alink = trim($alink);
        $alink =~ s/^x\s*//;
        $css .= '      a:active { color:'.$alink.' }';
        $css .= "\n";
      }
      if (defined $vlink) {
        $body->removeAttribute('vlink');
        $vlink = trim($vlink);
        $vlink =~ s/^x\s*//;
        $css .= '      a:visited { color:'.$vlink.' }';
        $css .= "\n";
      }
      $css .= '    ';
      $style->appendChild($doc->createTextNode($css));
    }
    if (scalar(keys %new_properties) > 0) {
      set_css_properties($body, \%new_properties);
    } elsif (!$body->hasAttributes) {
      $body->parentNode->removeChild($body);
    }
  }
}

# replaces <div align="center"> by <div style="text-align:center;">
# also for p and h1..h6
sub fix_align_attribute {
  my ($root) = @_;
  my @nodes = $root->getElementsByTagName('div');
  push(@nodes, $root->getElementsByTagName('p'));
  for (my $i=1; $i<=6; $i++) {
    push(@nodes, $root->getElementsByTagName('h'.$i));
  }
  foreach my $node (@nodes) {
    my $align = $node->getAttribute('align');
    if (defined $align) {
      $node->removeAttribute('align');
      $align = trim($align);
      if ($align ne '' && !defined get_css_property($node, 'text-align')) {
        set_css_property($node, 'text-align', lc($align));
      }
    }
  }
}

# replace center by a div or remove it if there is a table inside
sub replace_center {
  my ($root, $all_block) = @_;
  my $doc = $root->ownerDocument;
  my @centers = $root->getElementsByTagName('center');
  foreach my $center (@centers) {
    if ($center->getChildrenByTagName('table')->size() > 0) { # note: getChildrenByTagName is not DOM (LibXML specific)
      replace_by_children($center);
    } else {
      if ((!defined $center->previousSibling ||
          ($center->previousSibling->nodeType == XML_TEXT_NODE && $center->previousSibling->nodeValue =~ /^\s*$/ && !defined $center->previousSibling->previousSibling)) &&
          (!defined $center->nextSibling ||
          ($center->nextSibling->nodeType == XML_TEXT_NODE && $center->nextSibling->nodeValue =~ /^\s*$/ && !defined $center->nextSibling->nextSibling)) &&
          string_in_array(\@accepting_style, $center->parentNode->nodeName)) {
        # use CSS on the parent block and replace center by its children
        set_css_property($center->parentNode, 'text-align', 'center');
        replace_by_children($center);
      } else {
        # use p or div ? check if there is a block inside
        my $found_block = 0;
        for (my $child=$center->firstChild; defined $child; $child=$child->nextSibling) {
          if ($child->nodeType == XML_ELEMENT_NODE && string_in_array($all_block, $child->nodeName)) {
            $found_block = 1;
            last;
          }
        }
        my $new_node;
        if ($found_block) {
          $new_node = $doc->createElement('div');
          $new_node->setAttribute('style', 'text-align: center; margin: 0 auto');
        } else {
          $new_node = $doc->createElement('p');
          $new_node->setAttribute('style', 'text-align: center');
        }
        my $next;
        for (my $child=$center->firstChild; defined $child; $child=$next) {
          $next = $child->nextSibling;
          $center->removeChild($child);
          $new_node->appendChild($child);
        }
        $center->parentNode->replaceChild($new_node, $center);
      }
    }
  }
}

# replaces <nobr> by <span style="white-space:nowrap">
sub replace_nobr {
  my ($root) = @_;
  my @nobrs = $root->getElementsByTagName('nobr');
  foreach my $nobr (@nobrs) {
    if (!defined $nobr->previousSibling && !defined $nobr->nextSibling &&
        string_in_array(\@accepting_style, $nobr->parentNode->nodeName)) {
      # use CSS on the parent block
      set_css_property($nobr->parentNode, 'white-space', 'nowrap');
      replace_by_children($nobr);
    } else {
      $nobr->setNodeName('span');
      $nobr->setAttribute('style', 'white-space:nowrap');
    }
  }
}

# removes notsolved tags in the case <hintgroup showoncorrect="no"><notsolved>...</notsolved></hintgroup>
# and in the case <notsolved><hintgroup showoncorrect="no">...</hintgroup></notsolved>
sub remove_useless_notsolved {
  my ($root) = @_;
  my @hintgroups = $root->getElementsByTagName('hintgroup');
  foreach my $hintgroup (@hintgroups) {
    my $showoncorrect = get_non_empty_attribute($hintgroup, 'showoncorrect');
    if (!defined $showoncorrect || $showoncorrect eq 'no') {
      my @notsolveds = $hintgroup->getElementsByTagName('notsolved');
      foreach my $notsolved (@notsolveds) {
        replace_by_children($notsolved);
      }
    }
    my $parent = $hintgroup->parentNode;
    if ($parent->nodeName eq 'notsolved' && scalar(@{$parent->nonBlankChildNodes()}) == 1) {
      replace_by_children($parent);
    }
  }
}

# adds a paragraph inside if needed and calls fix_paragraph for all paragraphs (including new ones)
sub fix_paragraphs_inside {
  my ($node, $all_block) = @_;
  # blocks in which paragrahs will be added:
  my @blocks_with_p = ('loncapa','library','problem','part','problemtype','window','block','while','postanswerdate','preduedate','solved','notsolved','languageblock','translated','lang','instructorcomment','togglebox','standalone','form');
  my @fix_p_if_br_or_p = (@responses,'foil','item','text','label','hintgroup','hintpart','hint','web','windowlink','div','li','dd','td','th','blockquote');
  if ((string_in_array(\@blocks_with_p, $node->nodeName) && paragraph_needed($node)) ||
      (string_in_array(\@fix_p_if_br_or_p, $node->nodeName) && paragraph_inside($node))) {
    # if non-empty, add paragraphs where needed between all br and remove br
    # (it would be easier to just put everything in a p and fix it afterwards, but there are performance issues
    #  when a paragraph has many blocks directly inside)
    my $doc = $node->ownerDocument;
    my $p = undef;
    my @new_children = ();
    my $next;
    for (my $child=$node->firstChild; defined $child; $child=$next) {
      $next = $child->nextSibling;
      $node->removeChild($child);
      if ($child->nodeType == XML_ELEMENT_NODE && $child->nodeName eq 'br') {
        if (defined $p) {
          push(@new_children, $p);
        } else {
          push(@new_children, $doc->createElement('p'));
        }
        $p = undef;
      } elsif ($child->nodeType == XML_ELEMENT_NODE && string_in_array(\@inline_like_block, $child->nodeName)) {
        # inline_like_block: use the paragraph if there is one, otherwise do not create one
        if (defined $p) {
          $p->appendChild($child);
        } else {
          push(@new_children, $child);
        }
      } elsif ($child->nodeType == XML_ELEMENT_NODE && string_in_array($all_block, $child->nodeName)) {
        # these children are blocks and should not be in a paragraph
        if (defined $p) {
          push(@new_children, $p);
          $p = undef;
        }
        push(@new_children, $child);
      } elsif ($child->nodeType == XML_TEXT_NODE && $child->nodeValue =~ /^[ \t\f\n\r]*$/) {
        # blank text: add to paragraph if there is one and there is a next node, otherwise keep out of the paragraph
        if (defined $p) {
          if (defined $next) {
            $p->appendChild($child);
          } else {
            push(@new_children, $p);
            $p = undef;
            push(@new_children, $child);
          }
        } else {
          push(@new_children, $child);
        }
      } elsif ($child->nodeType == XML_TEXT_NODE ||
            $child->nodeType == XML_ELEMENT_NODE || $child->nodeType == XML_CDATA_SECTION_NODE ||
            $child->nodeType == XML_ENTITY_NODE || $child->nodeType == XML_ENTITY_REF_NODE) {
        # these children require a paragraph
        if (!defined $p) {
          $p = $doc->createElement('p');
        }
        $p->appendChild($child);
      } else {
        # these children do not require a paragraph (XML comments, PI)
        # -> do not move them in a new paragraph
        if (defined $p) {
          push(@new_children, $p);
          $p = undef;
        }
        push(@new_children, $child);
      }
    }
    if (defined $p) {
      push(@new_children, $p);
    }
    foreach my $child (@new_children) {
      $node->appendChild($child);
    }
  }
  # now fix the paragraphs everywhere, so that all inline nodes are inside a paragraph, and block nodes are outside
  my $next;
  for (my $child=$node->firstChild; defined $child; $child=$next) {
    $next = $child->nextSibling;
    if ($child->nodeType == XML_ELEMENT_NODE && defined $child->firstChild) {
      if ($child->nodeName eq 'p') {
        fix_paragraph($child, $all_block);
      } else {
        fix_paragraphs_inside($child, $all_block);
      }
    }
  }
}

# returns 1 if a paragraph is needed inside this node (assuming the parent can have paragraphs)
sub paragraph_needed {
  my ($node) = @_;
  for (my $child=$node->firstChild; defined $child; $child=$child->nextSibling) {
    if (($child->nodeType == XML_TEXT_NODE && $child->nodeValue !~ /^\s*$/) ||
        ($child->nodeType == XML_ELEMENT_NODE && !string_in_array(\@inline_like_block, $child->nodeName)) ||
        $child->nodeType == XML_CDATA_SECTION_NODE ||
        $child->nodeType == XML_ENTITY_NODE || $child->nodeType == XML_ENTITY_REF_NODE) {
      return(1);
    }
  }
  return(0);
}

# returns 1 if there is a paragraph or br in a child of this node, or inside an inline child
sub paragraph_inside {
  my ($node) = @_;
  # inline elements that can be split in half if there is a paragraph inside (currently all HTML):
  # (also used in first_block below)
  my @splitable_inline = ('span', 'a', 'strong', 'em' , 'b', 'i', 'sup', 'sub', 'code', 'kbd', 'samp', 'tt', 'ins', 'del', 'var', 'small', 'big', 'font', 'u');
  for (my $child=$node->firstChild; defined $child; $child=$child->nextSibling) {
    if ($child->nodeType == XML_ELEMENT_NODE) {
      my $name = $child->nodeName;
      if ($name eq 'p' || $name eq 'br') {
        return(1);
      } elsif (string_in_array(\@splitable_inline, $name)) {
        if (paragraph_inside($child)) {
          return(1);
        }
      }
    }
  }
  return(0);
}

# fixes paragraphs inside paragraphs (without a block in-between)
sub fix_paragraph {
  my ($p, $all_block) = @_;
  my $loop_right = 1; # this loops is to avoid out of memory errors with recurse, see below
  while ($loop_right) {
    $loop_right = 0;
    my $block = find_first_block($p, $all_block);
    if (defined $block) {
      my $trees = clone_ancestor_around_node($p, $block);
      my $doc = $p->ownerDocument;
      my $replacement = $doc->createDocumentFragment();
      my $left = $trees->{'left'};
      my $middle = $trees->{'middle'};
      my $right = $trees->{'right'};
      my $left_needs_p = 0; # 1 if it needs a paragraph (used to replace br later)
      
      if (defined $left) {
        # fix paragraphs inside, in case one of the descendants can have paragraphs inside (like numericalresponse/hintgroup):
        for (my $child=$left->firstChild; defined $child; $child=$child->nextSibling) {
          if ($child->nodeType == XML_ELEMENT_NODE) {
            fix_paragraphs_inside($child, $all_block);
          }
        }
        if (!paragraph_needed($left)) {
          # this was just blank text, comments or inline responses, it should not create a new paragraph
          my $next;
          for (my $child=$left->firstChild; defined $child; $child=$next) {
            $next = $child->nextSibling;
            $left->removeChild($child);
            $replacement->appendChild($child);
          }
        } else {
          $left_needs_p = 1;
          $replacement->appendChild($left);
        }
      }
      
      my $n = $middle->firstChild;
      while (defined $n) {
        if ($n->nodeType == XML_ELEMENT_NODE && (string_in_array($all_block, $n->nodeName) || $n->nodeName eq 'br')) {
          if ($n->nodeName eq 'p') {
            my $parent = $n->parentNode;
            # first apply recursion
            fix_paragraph($n, $all_block);
            # now the p might have been replaced by several nodes, which should replace the initial p
            my $next_block;
            for (my $block=$parent->firstChild; defined $block; $block=$next_block) {
              $next_block = $block->nextSibling;
              if ($block->nodeName eq 'p') {
                $parent->removeChild($block);
                # for each parent before $middle, clone in-between the p and its children (to preserve the styles)
                if (defined $block->firstChild) {
                  for (my $p=$parent; $p!=$middle; $p=$p->parentNode) {
                    my $newp = $p->cloneNode(0);
                    my $next;
                    for (my $child=$block->firstChild; defined $child; $child=$next) {
                      $next = $child->nextSibling;
                      $block->removeChild($child);
                      $newp->appendChild($child);
                    }
                    $block->appendChild($newp);
                  }
                }
              }
              $replacement->appendChild($block);
            }
          } else {
            # replace the whole p by this block, forgetting about intermediate inline elements
            $n->parentNode->removeChild($n);
            if ($n->nodeName eq 'br') {
              # replace a br by a paragraph if there was nothing before in the paragraph,
              # otherwise remove it because it already broke the paragraph in half
              if (!defined $left || !$left_needs_p) {
                $replacement->appendChild($middle);
              }
            } else {
              fix_paragraphs_inside($n, $all_block);
              $replacement->appendChild($n);
            }
          }
          last;
        }
        $n = $n->firstChild;
        if (defined $n && defined $n->nextSibling) {
          die "Error in post_xml.fix_paragraph: block not found";
        }
      }
      
      if (defined $right) {
        if ($block->nodeName eq 'p') {
          # remove attributes on the right paragraph
          my @attributelist = $right->attributes();
          foreach my $att (@attributelist) {
            $right->removeAttribute($att->nodeName);
          }
        }
        if ($right->firstChild->nodeType == XML_TEXT_NODE && $right->firstChild->nodeValue =~ /^[ \t\f\n\r]*$/) {
          # remove the first text node with whitespace only from the p, it should not trigger the creation of a p
          # (but take nbsp into account, so we should not use \s here)
          my $first = $right->firstChild;
          $right->removeChild($first);
          $replacement->appendChild($first);
        }
        if (defined $right->firstChild) {
          if (paragraph_needed($right)) {
            $replacement->appendChild($right);
            #fix_paragraph($right, $all_block); This is taking way too much memory for blocks with many children
            # -> loop instead of recurse
            $loop_right = 1;
          } else {
            # this was just blank text, comments or inline responses, it should not create a new paragraph
            my $next;
            for (my $child=$right->firstChild; defined $child; $child=$next) {
              $next = $child->nextSibling;
              $right->removeChild($child);
              $replacement->appendChild($child);
              # fix paragraphs inside, in case one of the descendants can have paragraphs inside (like numericalresponse/hintgroup):
              if ($child->nodeType == XML_ELEMENT_NODE) {
                fix_paragraphs_inside($child, $all_block);
              }
            }
          }
        }
      }
      
      $p->parentNode->replaceChild($replacement, $p);
      
      if ($loop_right) {
        $p = $right;
      }
      
    } else {
      # fix paragraphs inside, in case one of the descendants can have paragraphs inside (like numericalresponse/hintgroup):
      my $next;
      for (my $child=$p->firstChild; defined $child; $child=$next) {
        $next = $child->nextSibling;
        if ($child->nodeType == XML_ELEMENT_NODE) {
          fix_paragraphs_inside($child, $all_block);
        }
      }
    }
  }
}

sub find_first_block {
  my ($node, $all_block) = @_;
  # inline elements that can be split in half if there is a paragraph inside (currently all HTML):
  my @splitable_inline = ('span', 'a', 'strong', 'em' , 'b', 'i', 'sup', 'sub', 'code', 'kbd', 'samp', 'tt', 'ins', 'del', 'var', 'small', 'big', 'font', 'u');
  for (my $child=$node->firstChild; defined $child; $child=$child->nextSibling) {
    if ($child->nodeType == XML_ELEMENT_NODE) {
      if (string_in_array($all_block, $child->nodeName) || $child->nodeName eq 'br') {
        return($child);
      }
      if (string_in_array(\@splitable_inline, $child->nodeName)) {
        my $block = find_first_block($child, $all_block);
        if (defined $block) {
          return($block);
        }
      }
    }
  }
  return(undef);
}

# Creates clones of the ancestor containing the descendants before the node, at the node, and after the node.
# returns a hash with: left, middle, right (left and right can be undef)
sub clone_ancestor_around_node {
  my ($ancestor, $node) = @_;
  my $middle_node;
  my ($left, $middle, $right);
  for (my $child=$ancestor->firstChild; defined $child; $child=$child->nextSibling) {
    if ($child == $node || is_ancestor_of($child, $node)) {
      $middle_node = $child;
      last;
    }
  }
  if (!defined $middle_node) {
    die "error in split_ancestor_around_node: middle not found";
  }
  if (defined $middle_node->previousSibling) {
    $left = $ancestor->cloneNode(0);
    for (my $child=$ancestor->firstChild; $child != $middle_node; $child=$child->nextSibling) {
      $left->appendChild($child->cloneNode(1));
    }
  }
  $middle = $ancestor->cloneNode(0);
  if ($middle_node == $node) {
    $middle->appendChild($middle_node->cloneNode(1));
  } else {
    my $subres = clone_ancestor_around_node($middle_node, $node);
    my $subleft = $subres->{'left'};
    if (defined $subleft) {
      if (!defined $left) {
        $left = $ancestor->cloneNode(0);
      }
      $left->appendChild($subleft);
    }
    $middle->appendChild($subres->{'middle'});
    my $subright = $subres->{'right'};
    if (defined $subright) {
      $right = $ancestor->cloneNode(0);
      $right->appendChild($subright);
    }
  }
  if (defined $middle_node->nextSibling) {
    if (!defined $right) {
      $right = $ancestor->cloneNode(0);
    }
    for (my $child=$middle_node->nextSibling; defined $child; $child=$child->nextSibling) {
      $right->appendChild($child->cloneNode(1));
    }
  }
  my %result = ();
  $result{'left'} = $left;
  $result{'middle'} = $middle;
  $result{'right'} = $right;
  return(\%result);
}

sub is_ancestor_of {
  my ($n1, $n2) = @_;
  my $n = $n2->parentNode;
  while (defined $n) {
    if ($n == $n1) {
      return(1);
    }
    $n = $n->parentNode;
  }
  return(0);
}

# removes empty style elements and replaces the ones with only whitespaces inside by their content
# also remove hints that have become empty after empty style removal.
sub remove_empty_style {
  my ($root) = @_;
  # actually, preserve some elements like ins when they have whitespace, only remove if they are empty
  my @remove_if_empty = ('span', 'strong', 'em' , 'b', 'i', 'sup', 'sub', 'code', 'kbd', 'samp', 'tt', 'ins', 'del', 'var', 'small', 'big', 'font', 'u', 'hint');
  my @remove_if_blank = ('span', 'strong', 'em' , 'b', 'i', 'sup', 'sub', 'tt', 'var', 'small', 'big', 'font', 'u', 'hint');
  foreach my $name (@remove_if_empty) {
    my @nodes = $root->getElementsByTagName($name);
    while (scalar(@nodes) > 0) {
      my $node = pop(@nodes);
      if (!defined $node->firstChild) {
        my $parent = $node->parentNode;
        if (defined $node->previousSibling && $node->previousSibling->nodeType == XML_TEXT_NODE &&
            $node->previousSibling->nodeValue =~ /\$\S*$/) {
          # case $a<sup></sup>x
          my $value = $node->previousSibling->nodeValue;
          $value =~ s/\$(\S*)$/\$\{$1\}/;
          $node->previousSibling->setData($value);
        }
        $parent->removeChild($node);
        $parent->normalize();
        # now that we removed the node, check if the parent has become an empty style, and so on
        while (defined $parent && string_in_array(\@remove_if_empty, $parent->nodeName) && !defined $parent->firstChild) {
          my $grandparent = $parent->parentNode;
          $grandparent->removeChild($parent);
          remove_reference_from_array(\@nodes, $parent);
          $parent = $grandparent;
        }
      }
    }
  }
  foreach my $name (@remove_if_blank) {
    my @nodes = $root->getElementsByTagName($name);
    while (scalar(@nodes) > 0) {
      my $node = pop(@nodes);
      if (defined $node->firstChild && !defined $node->firstChild->nextSibling && $node->firstChild->nodeType == XML_TEXT_NODE) {
        # NOTE: careful, with UTF-8, \s matches non-breaking spaces and we want to preserve these
        if ($node->firstChild->nodeValue =~ /^[\t\n\f\r ]*$/) {
          my $parent = $node->parentNode;
          replace_by_children($node);
          $parent->normalize();
          # now that we removed the node, check if the parent has become a style with only whitespace, and so on
          while (defined $parent && string_in_array(\@remove_if_blank, $parent->nodeName) &&
              (!defined $parent->firstChild ||
              (!defined $parent->firstChild->nextSibling && $parent->firstChild->nodeType == XML_TEXT_NODE &&
              $parent->firstChild->nodeValue =~ /^^[\t\n\f\r ]*$/))) {
            my $grandparent = $parent->parentNode;
            replace_by_children($parent);
            remove_reference_from_array(\@nodes, $parent);
            $parent = $grandparent;
          }
        }
      }
    }
  }
}

# remove whitespace inside LON-CAPA elements that have an empty content-model (HTML ones are handled by html_to_xml)
sub fix_empty_lc_elements {
  my ($node) = @_;
  my @lcempty = ('arc','axis','backgroundplot','drawoptionlist','drawvectorsum','fill','functionplotrule','functionplotvectorrule','functionplotvectorsumrule','hiddenline','hiddensubmission','key','line','location','organicstructure','parameter','plotobject','plotvector','responseparam','spline','textline');
  if (string_in_array(\@lcempty, $node->nodeName)) {
    if (defined $node->firstChild && !defined $node->firstChild->nextSibling &&
        $node->firstChild->nodeType == XML_TEXT_NODE && $node->firstChild->nodeValue =~ /^\s*$/) {
      $node->removeChild($node->firstChild);
    }
    if (defined $node->firstChild) {
      print "Warning: a ".$node->nodeName." has something inside\n";
    }
    return;
  }
  for (my $child=$node->firstChild; defined $child; $child=$child->nextSibling) {
    if ($child->nodeType == XML_ELEMENT_NODE) {
      fix_empty_lc_elements($child);
    }
  }
}

# turn some attribute values into lowercase when they should be
sub lowercase_attribute_values {
  my ($root) = @_;
  my @with_yesno = (['radiobuttonresponse', ['randomize']],
                    ['optionresponse', ['randomize']],
                    ['matchresponse', ['randomize']],
                    ['itemgroup', ['randomize']],
                    ['rankresponse', ['randomize']],
                    ['functionplotresponse', ['xaxisvisible', 'yaxisvisible', 'gridvisible']],
                    ['backgroundplot', ['fixed']],
                    ['drawvectorsum', ['showvalue']],
                    ['textline', ['readonly']],
                    ['hint', ['showoncorrect']],
                    ['body', ['dir']],
                    ['img', ['encrypturl']],
                    ['form', ['method']],
                    ['input', ['type']]
                   );
  foreach my $el_attributes (@with_yesno) {
    my $el_name = $el_attributes->[0];
    my @elements = $root->getElementsByTagName($el_name);
    foreach my $element (@elements) {
      my $att_list = $el_attributes->[1];
      foreach my $att_name (@$att_list) {
        my $att_value = $element->getAttribute($att_name);
        if (!defined $att_value) {
          next;
        }
        if ($att_value eq 'yes' || $att_value eq 'no') {
          next;
        }
        if ($att_value =~ /\s*yes\s*/i) {
          $element->setAttribute($att_name, 'yes');
        } elsif ($att_value =~ /\s*no\s*/i) {
          $element->setAttribute($att_name, 'no');
        }
      }
    }
  }
}

# fixes spelling mistakes for numericalresponse/@unit
sub replace_numericalresponse_unit_attribute {
  my ($root) = @_;
  my @numericalresponses = $root->getElementsByTagName('numericalresponse');
  foreach my $numericalresponse (@numericalresponses) {
    if (defined $numericalresponse->getAttribute('units') && !defined $numericalresponse->getAttribute('unit')) {
      $numericalresponse->setAttribute('unit', $numericalresponse->getAttribute('units'));
      $numericalresponse->removeAttribute('units');
    }
  }
  
}

# Replaces &format and &prettyprint by <num> whenever possible.
# Also replaces &chemparse by <chem>.
# If the function call is enclosed in <display>, the <display> element is removed.
sub replace_functions_by_elements {
  my ($root) = @_;
  my $doc = $root->ownerDocument;
  my @preserve = ('script','answer','parse','m','tm','dtm','numericalhintscript'); # display is handled later
  my @all = $root->getElementsByTagName('*');
  foreach my $element (@all) {
    if (string_in_array(\@preserve, $element->nodeName)) {
      next;
    }
    my $changed = 0;
    my $next;
    for (my $child=$element->firstChild; defined $child; $child=$next) {
      $next = $child->nextSibling;
      if ($child->nodeType == XML_TEXT_NODE) {
        my $value = $child->nodeValue;
        if ($value =~ /^(.*)&(?:format|prettyprint)\((\$\{?[a-zA-Z0-9]*\}?(?:\[[^\]]*\])?|[0-9.]+)\s?,\s?(["'][,.\$]?[0-9][eEfFgGsS]["']|\$[a-zA-Z0-9]*)\)(.*)$/s) {
          # NOTE: we don't check for &prettyprint's 3rd argument (target), but it has not been seen outside of script elements.
          # NOTE: the format options ',' and '$' are not supported by &format in current LON-CAPA since rev 1.81 of default_homework.lcpm,
          #       but are supported by &prettyprint;
          #       if we use (like current LON-CAPA) &prettyprint for <num> implementation, it will change a few resulting documents
          #       (by making them display something they were probably intended to display, but which did not).
          #       Usage of <num> with &prettyprint instead of &format might also change the display when there is an exponent.
          my $before = $1;
          my $number = $2;
          my $format = $3;
          my $after = $4;
          $format =~ s/^['"]|['"]$//g;
          # do not change this if the parent is <display> and there are other things before or after &format
          if ($element->nodeName eq 'display' && (defined $child->previousSibling || defined $next ||
              $before !~ /^\s*$/ || $after !~ /^\s*$/)) {
            last;
          }
          my $replacement = $doc->createDocumentFragment();
          my $num = $doc->createElement('num');
          $num->setAttribute('format', $format);
          $num->appendChild($doc->createTextNode($number));
          if (length($before) > 0) {
            $replacement->appendChild($doc->createTextNode($before));
          }
          $replacement->appendChild($num);
          if (length($after) > 0) {
            $replacement->appendChild($doc->createTextNode($after));
          }
          $element->replaceChild($replacement, $child);
          $changed = 1;
          $next = $element->firstChild; # start over, there might be another &format in the same text node
        } elsif ($value =~ /^(.*)&chemparse\(([^'"()]*|'[^']*'|"[^"]*")\)(.*)$/s) {
          my $before = $1;
          my $reaction = $2;
          my $after = $3;
          $reaction =~ s/^'(.*)'$/$1/;
          $reaction =~ s/^"(.*)"$/$1/;
          if ($element->nodeName eq 'display' && (defined $child->previousSibling || defined $next ||
              $before !~ /^\s*$/ || $after !~ /^\s*$/)) {
            last;
          }
          my $replacement = $doc->createDocumentFragment();
          my $chem = $doc->createElement('chem');
          $chem->appendChild($doc->createTextNode($reaction));
          if (length($before) > 0) {
            $replacement->appendChild($doc->createTextNode($before));
          }
          $replacement->appendChild($chem);
          if (length($after) > 0) {
            $replacement->appendChild($doc->createTextNode($after));
          }
          $element->replaceChild($replacement, $child);
          $changed = 1;
          $next = $element->firstChild;
        }
      }
    }
    if ($changed && $element->nodeName eq 'display') {
      my $first = $element->firstChild;
      if ($first->nodeType == XML_ELEMENT_NODE && string_in_array(['num','chem'], $first->nodeName) &&
          !defined $first->nextSibling) {
        # remove useless display element
        replace_by_children($element);
      }
    }
  }
}

# pretty-print using im-memory DOM tree
sub pretty {
  my ($node, $all_block, $indent_level) = @_;
  my $doc = $node->ownerDocument;
  $indent_level ||= 0;
  my $type = $node->nodeType;
  if ($type == XML_ELEMENT_NODE) {
    my $name = $node->nodeName;
    if ((string_in_array($all_block, $name) || string_in_array(\@inline_like_block, $name)) &&
        !string_in_array(\@preserve_elements, $name)) {
      # make sure there is a newline at the beginning and at the end if there is anything inside
      if (defined $node->firstChild && !string_in_array(\@no_newline_inside, $name)) {
        my $first = $node->firstChild;
        if ($first->nodeType == XML_TEXT_NODE) {
          my $text = $first->nodeValue;
          if ($text !~ /^ *\n/) {
            $first->setData("\n" . $text);
          }
        } else {
          $node->insertBefore($doc->createTextNode("\n"), $first);
        }
        my $last = $node->lastChild;
        if ($last->nodeType == XML_TEXT_NODE) {
          my $text = $last->nodeValue;
          if ($text !~ /\n *$/) {
            $last->setData($text . "\n");
          }
        } else {
          $node->appendChild($doc->createTextNode("\n"));
        }
      }
      
      # indent and make sure there is a newline before and after a block element
      my $newline_indent = "\n".(' ' x (2*($indent_level + 1)));
      my $newline_indent_last = "\n".(' ' x (2*$indent_level));
      my $next;
      for (my $child=$node->firstChild; defined $child; $child=$next) {
        $next = $child->nextSibling;
        if ($child->nodeType == XML_ELEMENT_NODE) {
          if (string_in_array($all_block, $child->nodeName) || string_in_array(\@inline_like_block, $child->nodeName)) {
            # make sure there is a newline before and after a block element
            if (defined $child->previousSibling && $child->previousSibling->nodeType == XML_TEXT_NODE) {
              my $prev = $child->previousSibling;
              my $text = $prev->nodeValue;
              if ($text !~ /\n *$/) {
                $prev->setData($text . $newline_indent);
              }
            } else {
              $node->insertBefore($doc->createTextNode($newline_indent), $child);
            }
            if (defined $next && $next->nodeType == XML_TEXT_NODE) {
              my $text = $next->nodeValue;
              if ($text !~ /^ *\n/) {
                $next->setData($newline_indent . $text);
              }
            } else {
              $node->insertAfter($doc->createTextNode($newline_indent), $child);
            }
          }
          pretty($child, $all_block, $indent_level+1);
        } elsif ($child->nodeType == XML_TEXT_NODE) {
          my $text = $child->nodeValue;
          # collapse newlines
          $text =~ s/\n([\t ]*\n)+/\n/g;
          # indent and remove spaces and tabs before newlines
          if (defined $next) {
            $text =~ s/[\t ]*\n[\t ]*/$newline_indent/ge;
          } else {
            $text =~ s/[\t ]*\n[\t ]*/$newline_indent/ge;
            $text =~ s/[\t ]*\n[\t ]*$/$newline_indent_last/e;
          }
          $child->setData($text);
        }
      }
      
      # removes whitespace at the beginning and end of p td, th and li (except for nbsp at the beginning)
      my @to_trim = ('p','td','th','li');
      if (string_in_array(\@to_trim, $name) && defined $node->firstChild && $node->firstChild->nodeType == XML_TEXT_NODE) {
        my $text = $node->firstChild->nodeValue;
        $text =~ s/^[ \t\f\n\r]*//;
        if ($text eq '') {
          $node->removeChild($node->firstChild);
        } else {
          $node->firstChild->setData($text);
        }
      }
      if (string_in_array(\@to_trim, $name) && defined $node->lastChild && $node->lastChild->nodeType == XML_TEXT_NODE) {
        my $text = $node->lastChild->nodeValue;
        $text =~ s/\s*$//;
        if ($text eq '') {
          $node->removeChild($node->lastChild);
        } else {
          $node->lastChild->setData($text);
        }
      }
    } elsif (string_in_array(\@preserve_elements, $name)) {
      # collapse newlines at the beginning and the end of scripts
      if (defined $node->firstChild && $node->firstChild->nodeType == XML_TEXT_NODE) {
        my $text = $node->firstChild->nodeValue;
        $text =~ s/^\n( *\n)+/\n/;
        if ($text eq '') {
          $node->removeChild($node->firstChild);
        } else {
          $node->firstChild->setData($text);
        }
      }
      if (defined $node->lastChild && $node->lastChild->nodeType == XML_TEXT_NODE) {
        my $text = $node->lastChild->nodeValue;
        $text =~ s/\n( *\n)+$/\n/;
        if ($text eq '') {
          $node->removeChild($node->lastChild);
        } else {
          $node->lastChild->setData($text);
        }
      }
    }
  }
}

sub replace_tm_dtm {
  my ($root) = @_;
  my $doc = $root->ownerDocument;
  my @elements = $root->getElementsByTagName('tm');
  push(@elements, $root->getElementsByTagName('dtm'));
  foreach my $element (@elements) {
    my $first = $element->firstChild;
    if (defined $first && $first->nodeType == XML_TEXT_NODE) {
      my $text = $first->nodeValue;
      if ($element->nodeName eq 'tm') {
        $first->setData('$'.$text.'$');
      } else {
        $first->setData('$$'.$text.'$$');
      }
    }
    $element->setNodeName('m');
  }
}


######## utilities ########

##
# Trims a string (really, this should be built-in in Perl, this is ridiculous, ugly and slow)
# @param {string} s - the string to trim
# @returns the trimmed string
##
sub trim {
  my ($s) = @_;
  $s =~ s/^\s+//;
  $s =~ s/\s+$//;
  return($s);
}

##
# Tests if a string is in an array (using eq) (to avoid Smartmatch warnings with $value ~~ @array)
# @param {Array<string>} array - reference to the array of strings
# @param {string} value - the string to look for
# @returns 1 if found, 0 otherwise
##
sub string_in_array {
  my ($array, $value) = @_;
  foreach my $v (@{$array}) {
    if ($v eq $value) {
      return 1;
    }
  }
  return 0;
}

##
# Tests if an object is in an array (using ==)
# @param {Array<Object>} array - reference to the array of references
# @param {Object} ref - the reference to look for
# @returns 1 if found, 0 otherwise
##
sub reference_in_array {
  my ($array, $ref) = @_;
  foreach my $v (@{$array}) {
    if ($v == $ref) {
      return 1;
    }
  }
  return 0;
}

##
# returns the index of a string in an array
# @param {Array<Object>} array - reference to the array of strings
# @param {string} s - the string to look for (using eq)
# @returns the index if found, -1 otherwise
##
sub index_of_string {
  my ($array, $s) = @_;
  for (my $i=0; $i<scalar(@{$array}); $i++) {
    if ($array->[$i] eq $s) {
      return $i;
    }
  }
  return -1;
}

##
# returns the index of a reference in an array
# @param {Array<Object>} array - reference to the array of references
# @param {Object} ref - the reference to look for
# @returns the index if found, -1 otherwise
##
sub index_of_reference {
  my ($array, $ref) = @_;
  for (my $i=0; $i<scalar(@{$array}); $i++) {
    if ($array->[$i] == $ref) {
      return $i;
    }
  }
  return -1;
}

##
# if found, removes a string from an array, otherwise do nothing
# @param {Array<string>} array - reference to the array of string
# @param {string} s - the string to look for (using eq)
##
sub remove_string_from_array {
  my ($array, $s) = @_;
  my $index = index_of_string($array, $s);
  if ($index != -1) {
    splice(@$array, $index, 1);
  }
}

##
# if found, removes a reference from an array, otherwise do nothing
# @param {Array<Object>} array - reference to the array of references
# @param {Object} ref - the reference to look for
##
sub remove_reference_from_array {
  my ($array, $ref) = @_;
  my $index = index_of_reference($array, $ref);
  if ($index != -1) {
    splice(@$array, $index, 1);
  }
}

##
# replaces a node by its children
# @param {Node} node - the DOM node
##
sub replace_by_children {
  my ($node) = @_;
  my $parent = $node->parentNode;
  my $next;
  my $previous;
  for (my $child=$node->firstChild; defined $child; $child=$next) {
    $next = $child->nextSibling;
    if ((!defined $previous || !defined $next) &&
        $child->nodeType == XML_TEXT_NODE && $child->nodeValue =~ /^\s*$/) {
      next; # do not keep first and last whitespace nodes
    } else {
      if (!defined $previous && $child->nodeType == XML_TEXT_NODE) {
        # remove whitespace at the beginning
        my $value = $child->nodeValue;
        $value =~ s/^\s+//;
        $child->setData($value);
      }
      if (!defined $next && $child->nodeType == XML_TEXT_NODE) {
        # and at the end
        my $value = $child->nodeValue;
        $value =~ s/\s+$//;
        $child->setData($value);
      }
    }
    $node->removeChild($child);
    $parent->insertBefore($child, $node);
    $previous = $child;
  }
  $parent->removeChild($node);
}

##
# returns the trimmed attribute value if the attribute exists and is not blank, undef otherwise
# @param {Node} node - the DOM node
# @param {string} attribute_name - the attribute name
##
sub get_non_empty_attribute {
  my ($node, $attribute_name) = @_;
  my $value = $node->getAttribute($attribute_name);
  if (defined $value && $value !~ /^\s*$/) {
    $value = trim($value);
    return($value);
  }
  return(undef);
}

##
# Returns a CSS property value from the style attribute of the element, or undef if not defined
# @param {Element} el - the DOM element
# @param {string} property_name - the CSS property name
##
sub get_css_property {
  my ($el, $property_name) = @_;
  my $style = $el->getAttribute('style');
  if (defined $style) {
    $style =~ s/^\s*;\s*//;
    $style =~ s/\s*;\s*$//;
  } else {
    $style = '';
  }
  my @pairs = split(';', $style);
  foreach my $pair (@pairs) {
    my @name_value = split(':', $pair);
    if (scalar(@name_value) != 2) {
      next;
    }
    my $name = trim($name_value[0]);
    my $value = trim($name_value[1]);
    if (lc($name) eq $property_name) {
      return($value); # return the first one found
    }
  }
  return(undef);
}

##
# Returns the reference to a hash CSS property name => value from the style attribute of the element.
# Returns an empty list if the style attribute is not defined,
# @param {Element} el - the DOM element
# @return {Hash<string, string>} reference to the hash  property name => property value
##
sub get_css_properties {
  my ($el) = @_;
  my $style = $el->getAttribute('style');
  if (defined $style) {
    $style =~ s/^\s*;\s*//;
    $style =~ s/\s*;\s*$//;
  } else {
    $style = '';
  }
  my @pairs = split(';', $style);
  tie (my %hash, 'Tie::IxHash', ());
  foreach my $pair (@pairs) {
    my @name_value = split(':', $pair);
    if (scalar(@name_value) != 2) {
      next;
    }
    my $name = trim($name_value[0]);
    my $value = trim($name_value[1]);
    if (defined $hash{$name}) {
      # duplicate property in the style attribute: keep only the last one
      delete $hash{$name};
    }
    $hash{$name} = $value;
  }
  return(\%hash);
}

##
# Sets a CSS property in the style attribute of an element
# @param {Element} el - the DOM element
# @param {string} property_name - the CSS property name
# @param {string} property_value - the CSS property value
##
sub set_css_property {
  my ($el, $property_name, $property_value) = @_;
  my $hash_ref = { $property_name => $property_value };
  set_css_properties($el, $hash_ref);
}

##
# Sets several CSS properties in the style attribute of an element
# @param {Element} el - the DOM element
# @param {Hash<string, string>} properties - reference to the hash property name => property value
##
sub set_css_properties {
  my ($el, $properties) = @_;
  my $hash = get_css_properties($el);
  foreach my $property_name (keys %$properties) {
    my $property_value = $properties->{$property_name};
    if (defined $hash->{$property_name}) {
      delete $hash->{$property_name}; # to add the new one at the end
    }
    $hash->{$property_name} = $property_value;
  }
  my $style = '';
  foreach my $key (keys %$hash) {
    $style .= $key.':'.$hash->{$key}.'; ';
  }
  $style =~ s/; $//;
  $el->setAttribute('style', $style);
}

1;
__END__

Index: modules/damieng/clean_xml/pre_xml.pm
+++ modules/damieng/clean_xml/pre_xml.pm
#!/usr/bin/perl

package pre_xml;

use strict;
use utf8;

use Encode;
use Encode::Byte;
use Encode::Guess;

# list of elements inside which < and > might not be turned into entities
# unfortunately, answer can sometimes contain the elements vector and value...
my @cdata_elements = ('answer', 'm', 'display', 'parse'); # not script because the HTML parser will handle it


# Reads a LON-CAPA 2 file, guesses the encoding, fixes characters in cdata_elements, fixes HTML entities,
# and returns the converted text.
sub pre_xml {
  my ($filepath) = @_;
  
  my $lines = guess_encoding_and_read($filepath);

  remove_control_characters($lines);
  
  fix_cdata_elements($lines);

  fix_html_entities($lines);
  
  fix_missing_quotes($lines);
  
  fix_empty_li($lines);
  
  remove_doctype($lines);
  
  add_root($lines, $filepath);
  
  return(\join('', @$lines));
}


##
# Tries to guess the character encoding, and returns the lines as decoded text.
# Requires Encode::Byte.
##
sub guess_encoding_and_read {
  my ($fn) = @_;
  no warnings "utf8";
  local $/ = undef;
  open(my $fh, "<", $fn) or die "cannot read $fn: $!";
  binmode $fh;
  my $data = <$fh>; # we need to read the whole file to test if font is a block or inline element
  close $fh;
  
  if (index($data, '<') == -1) {
    die "This file has no markup !";
  }
  
  # try to get a charset from a meta at the beginning of the file
  my $beginning = substr($data, 0, 1024); # to avoid a full match; hopefully we won't cut the charset in half
  if ($beginning =~ /<meta[^>]*charset\s?=\s?([^\n>"';]*)/i) {
    my $meta_charset = $1;
    if ($meta_charset ne '') {
      if ($meta_charset =~ /iso-?8859-?1/i) {
        # usually a lie
        $meta_charset = 'cp1252';
      }
      # now try to decode using that encoding
      my $decoder = guess_encoding($data, ($meta_charset));
      if (ref($decoder)) {
        my $decoded = $decoder->decode($data);
        my @lines = split(/^/m, $decoded);
        return \@lines;
      } else {
        print "Warning: decoding did not work with the charset defined by the meta ($meta_charset)\n";
      }
    }
  }
  
  my $decoded;
  if (length($data) > 0) {
    # NOTE: this list is too ambigous, Encode::Guess refuses to even try a guess
    #Encode::Guess->set_suspects(qw/ascii UTF-8 iso-8859-1 MacRoman cp1252/);
    # by default Encode::Guess uses ascii, utf8 and UTF-16/32 with BOM
    my $decoder = Encode::Guess->guess($data);
    if (ref($decoder)) {
      $decoded = $decoder->decode($data);
      # NOTE: this seems to accept binary files sometimes (conversion will fail later because it is not really UTF-8)
    } else {
      print "Warning: encoding is not UTF-8 for $fn";
      
      # let's try iso-2022-jp first
      $decoder = Encode::Guess->guess($data, 'iso-2022-jp');
      if (ref($decoder)) {
        $decoded = $decoder->decode($data);
        print "; using iso-2022-jp\n";
      } else {
        # NOTE: cp1252 is identical to iso-8859-1 but with additionnal characters in range 128-159
        # instead of control codes. We can assume that these control codes are not used, so there
        # is no need to test for iso-8859-1.
        # The main problem here is to distinguish between cp1252 and MacRoman.
        # see http://www.alanwood.net/demos/charsetdiffs.html#f
        my $decoded_windows = decode('cp1252', $data);
        my $decoded_mac = decode('MacRoman', $data);
        # try to use frequent non-ASCII characters to distinguish the encodings (languages: mostly German, Spanish, Portuguese)
        # í has been removed because it conflicts with ’ and ’ is more frequent
        # ± has been removed because it is, suprisingly, the same code in both encodings !
        my $score_windows = $decoded_windows =~ tr/ßáàäâãçéèêëñóöôõúüÄÉÑÖÜ¿¡‘’“” °½–—…§//;
        my $score_mac = $decoded_mac =~ tr/ßáàäâãçéèêëñóöôõúüÄÉÑÖÜ¿¡‘’“” °½–—…§//;
        # check newlines too (\r on MacOS < X, \r\n on Windows)
        my $ind_cr = index($data, "\r");
        if ($ind_cr != -1) {
          if (substr($data, $ind_cr + 1, 1) eq "\n") {
            $score_windows++;
          } else {
            $score_mac++;
          }
        }
        if ($score_windows >= $score_mac) {
          $decoded = $decoded_windows;
          print "; guess=cp1252 ($score_windows cp1252 >= $score_mac MacRoman)\n";
        } else {
          print "; guess=MacRoman ($score_mac MacRoman > $score_windows cp1252)\n";
          $decoded = $decoded_mac;
        }
      }
    }
  } else {
    $decoded = '';
  }
  my @lines = split(/^/m, $decoded);
  return \@lines;
}


##
# Removes some control characters
# @param {Array<string>} lines
##
sub remove_control_characters {
  my ($lines) = @_;
  foreach my $line (@{$lines}) {
    $line =~ s/[\x00-\x07\x0B\x0C\x0E-\x1F]//g;
    $line =~ s/&#[0-7];//g;
    $line =~ s/&#1[4-9];//g;
    $line =~ s/&#2[0-9];//g;
  }
}

##
# Replaces < and > characters by < and > in cdata elements (listed in @cdata_elements).
# EXCEPT for answer when it's inside numericalresponse or formularesponse.
# @param {Array<string>} lines
##
sub fix_cdata_elements {
  my ($lines) = @_;
  my $i = 0;
  my $j = 0;
  my $tag = '';
  my $type;
  my $in_numericalresponse = 0;
  my $in_formularesponse = 0;
  my $in_script = 0;
  ($tag, $type, $i, $j) = next_tag($lines, $i, $j);
  while ($tag ne '') {
    if ($tag eq 'numericalresponse') {
      if ($type eq 'start') {
        $in_numericalresponse = 1;
      } else {
        $in_numericalresponse = 0;
      }
    } elsif ($tag eq 'formularesponse') {
      if ($type eq 'start') {
        $in_formularesponse = 1;
      } else {
        $in_formularesponse = 0;
      }
    } elsif ($tag eq 'script') {
      if ($type eq 'start') {
        $in_script = 1;
      } else {
        $in_script = 0;
      }
    }
    if ($type eq 'start' && in_array_ignore_case(\@cdata_elements, $tag) && !$in_script &&
        ($tag ne 'answer' || (!$in_numericalresponse && !$in_formularesponse))) {
      my $cde = $tag;
      my $line = $lines->[$i];
      $j = index($line, '>', $j+1) + 1;
      my $stop = 0;
      while (!$stop && $i < scalar(@{$lines})) {
        my $indinf = index($line, '<', $j);
        if ($indinf != -1 && index($line, '<![CDATA[', $indinf) == $indinf) {
          $i++;
          $line = $lines->[$i];
          $j = 0;
          last;
        }
        my $indsup = index($line, '>', $j);
        if ($indinf != -1 && $indsup != -1 && $indinf < $indsup) {
          my $test = substr($line, $indinf + 1, $indsup - ($indinf + 1));
          $test =~ s/^\s+|\s+$//g ;
          if ($test eq '/'.$cde) {
            $stop = 1;
            $j = $indsup;
          # this is commented because of markup like <display>&web(' ','','<p>')</display>
          #} elsif ($test =~ /^[a-zA-Z\/]$/) {
          #  $j = $indsup + 1;
          } else {
            $line = substr($line, 0, $indinf).'<'.substr($line, $indinf+1);
            $lines->[$i] = $line;
          }
        } elsif ($indinf != -1 && $indsup == -1) {
          $line = substr($line, 0, $indinf).'<'.substr($line, $indinf+1);
          $lines->[$i] = $line;
        } elsif ($indsup != -1 && ($indinf == -1 || $indsup < $indinf)) {
          $line = substr($line, 0, $indsup).'>'.substr($line, $indsup+1);
          $lines->[$i] = $line;
        } else {
          $i++;
          $line = $lines->[$i];
          $j = 0;
        }
      }
    }
    $j++;
    ($tag, $type, $i, $j) = next_tag($lines, $i, $j);
  }
}


##
# Replaces HTML entities (they are not XML unless a DTD is used, which is no longer recommanded for XHTML).
# @param {Array<string>} lines
##
sub fix_html_entities {
  my ($lines) = @_;
  foreach my $line (@{$lines}) {
    # html_to_xml is converting named entities before 255 (see HTML parser dtext)
    # Assuming Windows encoding (Unicode entities are not before 160 and are the same between 160 and 255):
    $line =~ s/€|&#x80;/€/g;
    $line =~ s/‚|&#x82;/‚/g;
    $line =~ s/„|&#x84;/„/g;
    $line =~ s/…|&#x85;/…/g;
    $line =~ s/†|&#x86;/†/g;
    $line =~ s/‡|&#x87;/‡/g;
    $line =~ s/ˆ|&#x88;/ˆ/g;
    $line =~ s/‰|&#x89;/‰/g;
    $line =~ s/‹|&#x8B;/‹/g;
    $line =~ s/‘|&#x91;/‘/g;
    $line =~ s/’|&#x92;/’/g;
    $line =~ s/“|&#x93;/“/g;
    $line =~ s/”|&#x94;/”/g;
    $line =~ s/•|&#x95;/•/g;
    $line =~ s/–|&#x96;/–/g;
    $line =~ s/—|&#x97;/—/g;
    $line =~ s/˜|&#x98;/Ëœ/g;
    $line =~ s/™|&#x99;/â„¢/g;
    $line =~ s/›|&#x9B;/›/g;
    $line =~ s/œ|&#x9C;/Å“/g;
  }
}


# Tries to fix things like <font color="#990000" face="Verdana,>
# without breaking <a b="c>d">
# This is only fixing tags when there is a single tag in a line (it is impossible to fix in the general case).
# Also transforms <a b="c> <d e=" into <a b="c"><d e=" ,
# and (no markup before)<a b="c> (no quote after) into <a b="c"> .
sub fix_missing_quotes {
  my ($lines) = @_;
  foreach my $line (@{$lines}) {
    my $n_inf = $line =~ tr/<//;
    my $n_sup = $line =~ tr/>//;
    if ($n_inf == 1 && $n_sup == 1) {
      my $ind_inf = index($line, '<');
      my $ind_sup = index($line, '>');
      if ($ind_inf != -1 && $ind_sup != -1 && $ind_inf < $ind_sup) {
        my $n_quotes = substr($line, $ind_inf, $ind_sup) =~ tr/"//;
        if ($n_quotes % 2 != 0) {
          # add a quote before > when there is an odd number of quotes inside <>
          $line =~ s/>/">/;
        }
      }
    }
    $line =~ s/(<[a-zA-Z]+ [a-zA-Z]+="[^"<>\s]+)(>\s*<[a-zA-Z]+ [a-zA-Z]+=")/$1"$2/;
    $line =~ s/^([^"<>]*<[a-zA-Z]+ [a-zA-Z]+="[^"<>\s]+)(>[^"]*)$/$1"$2/;
  }
}


# Replaces <li/> by <li> (the end tag will be added in html_to_xml
sub fix_empty_li {
  my ($lines) = @_;
  foreach my $line (@{$lines}) {
    $line =~ s/<li\s?\/>/<li>/;
  }
}


# remove doctypes, without assuming they are at the beginning
sub remove_doctype {
  my ($lines) = @_;
  foreach my $line (@{$lines}) {
    $line =~ s/<!DOCTYPE[^>]*>//;
  }
}


# Adds a problem, library or html root element, enclosing things outside of the problem element.
# (any extra root element will be removed in post_xml, but this ensures one is added as root if missing).
sub add_root {
  my ($lines, $filepath) = @_;
  my $root_name;
  if ($filepath =~ /\.library$/i) {
    $root_name = 'library';
  } elsif ($filepath =~ /\.html?$/i) {
    $root_name = 'html';
  } else {
    $root_name = 'problem';
  }
  if ($root_name eq 'library') {
    foreach my $line (@{$lines}) {
      if ($line =~ /^\s*<[a-z]/) {
        last;
      }
      if ($line !~ /^\s*$/) {
        die "this library does not start with a tag, it might be a scriptlib";
      }
    }
  }
  my $line1 = $lines->[0];
  $line1 =~ s/<\?.*\?>//; # remove any PI, it would cause problems later anyway
  $line1 = "<$root_name>".$line1;
  $lines->[0] = $line1;
  $lines->[scalar(@$lines)-1] = $lines->[scalar(@$lines)-1]."</$root_name>";
}


##
# Returns information about the next tag, starting at line number and char number.
# Assumes the markup is well-formed and there is no CDATA,
# which is not always true (like inside script), so results might be wrong sometimes.
# It is however useful to avoid unnecessary changes in the document (using a parser to
# do read/write for the whole document would mess up non well-formed documents).
# @param {Array<string>} lines
# @param {int} line_number - line number to start at
# @param {int} char_number - char number to start at on the line
# @returns (tag, type, line_number, char_number)
##
sub next_tag {
  my ($lines, $i, $j ) = @_;
  my $i2 = $i;
  my $j2 = $j;
  while ($i2 < scalar(@{$lines})) {
    my $line = $lines->[$i2];
    $j2 = index($line, '<', $j2);
    #TODO: handle comments
    while ($j2 != -1) {
      my $ind_slash = index($line, '/', $j2);
      my $ind_sup = index($line, '>', $j2);
      my $ind_space = index($line, ' ', $j2);
      my $type;
      my $tag;
      if ($ind_slash == $j2 + 1 && $ind_sup != -1) {
        $type = 'end';
        $tag = substr($line, $j2 + 2, $ind_sup - ($j2 + 2));
      } elsif ($ind_slash != -1 && $ind_sup != -1 && $ind_slash == $ind_sup - 1) {
        $type = 'empty';
        if ($ind_space != -1 && $ind_space < $ind_sup) {
          $tag = substr($line, $j2 + 1, $ind_space - ($j2 + 1));
        } else {
          $tag = substr($line, $j2 + 1, $ind_slash - ($j2 + 1));
        }
      } elsif ($ind_sup != -1) {
        $type = 'start';
        if ($ind_space != -1 && $ind_space < $ind_sup) {
          $tag = substr($line, $j2 + 1, $ind_space - ($j2 + 1));
        } else {
          $tag = substr($line, $j2 + 1, $ind_sup - ($j2 + 1));
        }
      } else {
        $tag = ''
      }
      if ($tag ne '') {
        return ($tag, $type, $i2, $j2);
      }
      $j2 = index($line, '<', $j2 + 1);
    }
    $i2++;
    $j2 = 0;
  }
  return ('', '', 0, 0);
}

##
# Tests if a string is in an array, ignoring case
##
sub in_array_ignore_case {
  my ($array, $value) = @_;
  my $lcvalue = lc($value);
  foreach my $v (@{$array}) {
    if (lc($v) eq $lcvalue) {
      return 1;
    }
  }
  return 0;
}

1;
__END__

Index: modules/damieng/clean_xml/validate_xml.pl
+++ modules/damieng/clean_xml/validate_xml.pl
#!/usr/bin/perl

# Validates a file or directory against loncapa.xsd with libxml2

use strict;
use utf8;
use warnings;

use File::Basename;
use Try::Tiny;
use XML::LibXML;


binmode(STDOUT, ':encoding(UTF-8)');
binmode(STDERR, ':encoding(UTF-8)');

if (scalar(@ARGV) != 1) {
  print STDERR "Usage: perl validate_xml.pl file|directory\n";
  exit(1);
}

# find the command-line argument encoding
use I18N::Langinfo qw(langinfo CODESET);
my $codeset = langinfo(CODESET);
use Encode qw(decode);
@ARGV = map { decode $codeset, $_ } @ARGV;

my $pathname = "$ARGV[0]";

my $script_dir = dirname(__FILE__);
my $xmlschema = XML::LibXML::Schema->new(location => $script_dir.'/loncapa.xsd');

if (-d "$pathname") {
  validate_dir($pathname);
} elsif (-f $pathname) {
  validate_file($pathname);
}


# Validates a directory recursively, selecting only .(problem|exam|survey|html|library).xml files.
sub validate_dir {
  my ($dirpath) = @_;
  
  opendir (my $dh, $dirpath) or die $!;
  while (my $entry = readdir($dh)) {
    next if ($entry =~ m/^\./); # ignore entries starting with a period
    my $pathname = $dirpath.'/'.$entry;
    if (-d $pathname) {
      validate_dir($pathname);
    } elsif (-f $pathname) {
      if ($pathname =~ /\.(problem|exam|survey|html?|library)\.xml$/) {
        validate_file($pathname);
      }
    }
  }
  closedir($dh);
}

# Validates a file against loncapa.xsd with libxml2
sub validate_file {
  my ($pathname) = @_;
  
  my $doc = XML::LibXML->load_xml(location => $pathname);
  try {
    $xmlschema->validate($doc);
    print "$pathname is valid\n";
  } catch {
    $_ =~ s/%20/ /g;
    print "$_\n";
  }
}

Index: modules/damieng/clean_xml/xml.xsd
+++ modules/damieng/clean_xml/xml.xsd
<?xml version='1.0'?>
<!-- <!DOCTYPE xs:schema PUBLIC "-//W3C//DTD XMLSCHEMA 200102//EN" "XMLSchema.dtd" > -->
<xs:schema targetNamespace="http://www.w3.org/XML/1998/namespace" xmlns:xs="http://www.w3.org/2001/XMLSchema" xml:lang="en">

 <xs:annotation>
  <xs:documentation>
   See http://www.w3.org/XML/1998/namespace.html and
   http://www.w3.org/TR/REC-xml for information about this namespace.

    This schema document describes the XML namespace, in a form
    suitable for import by other schema documents.  

    Note that local names in this namespace are intended to be defined
    only by the World Wide Web Consortium or its subgroups.  The
    following names are currently defined in this namespace and should
    not be used with conflicting semantics by any Working Group,
    specification, or document instance:

    base (as an attribute name): denotes an attribute whose value
         provides a URI to be used as the base for interpreting any
         relative URIs in the scope of the element on which it
         appears; its value is inherited.  This name is reserved
         by virtue of its definition in the XML Base specification.

    id   (as an attribute name): denotes an attribute whose value
         should be interpreted as if declared to be of type ID.
         The xml:id specification is not yet a W3C Recommendation,
         but this attribute is included here to facilitate experimentation
         with the mechanisms it proposes.  Note that it is _not_ included
         in the specialAttrs attribute group.

    lang (as an attribute name): denotes an attribute whose value
         is a language code for the natural language of the content of
         any element; its value is inherited.  This name is reserved
         by virtue of its definition in the XML specification.
  
    space (as an attribute name): denotes an attribute whose
         value is a keyword indicating what whitespace processing
         discipline is intended for the content of the element; its
         value is inherited.  This name is reserved by virtue of its
         definition in the XML specification.

    Father (in any context at all): denotes Jon Bosak, the chair of 
         the original XML Working Group.  This name is reserved by 
         the following decision of the W3C XML Plenary and 
         XML Coordination groups:

             In appreciation for his vision, leadership and dedication
             the W3C XML Plenary on this 10th day of February, 2000
             reserves for Jon Bosak in perpetuity the XML name
             xml:Father
  </xs:documentation>
 </xs:annotation>

 <xs:annotation>
  <xs:documentation>This schema defines attributes and an attribute group
        suitable for use by
        schemas wishing to allow xml:base, xml:lang or xml:space attributes
        on elements they define.

        To enable this, such a schema must import this schema
        for the XML namespace, e.g. as follows:
        <schema . . .>
         . . .
         <import namespace="http://www.w3.org/XML/1998/namespace"
                    schemaLocation="http://www.w3.org/2001/03/xml.xsd"/>

        Subsequently, qualified reference to any of the attributes
        or the group defined below will have the desired effect, e.g.

        <type . . .>
         . . .
         <attributeGroup ref="xml:specialAttrs"/>
 
         will define a type which will schema-validate an instance
         element with any of those attributes</xs:documentation>
 </xs:annotation>

 <xs:annotation>
  <xs:documentation>In keeping with the XML Schema WG's standard versioning
   policy, this schema document will persist at
   http://www.w3.org/2004/10/xml.xsd.
   At the date of issue it can also be found at
   http://www.w3.org/2001/xml.xsd.
   The schema document at that URI may however change in the future,
   in order to remain compatible with the latest version of XML Schema
   itself, or with the XML namespace itself.  In other words, if the XML
   Schema or XML namespaces change, the version of this document at
   http://www.w3.org/2001/xml.xsd will change
   accordingly; the version at
   http://www.w3.org/2004/10/xml.xsd will not change.
  </xs:documentation>
 </xs:annotation>

 <xs:attribute name="lang" type="xs:language">
  <xs:annotation>
   <xs:documentation>Attempting to install the relevant ISO 2- and 3-letter
         codes as the enumerated possible values is probably never
         going to be a realistic possibility.  See
         RFC 3066 at http://www.ietf.org/rfc/rfc3066.txt and the IANA registry
         at http://www.iana.org/assignments/lang-tag-apps.htm for
         further information.</xs:documentation>
  </xs:annotation>
 </xs:attribute>

 <xs:attribute name="space">
  <xs:simpleType>
   <xs:restriction base="xs:NCName">
    <xs:enumeration value="default"/>
    <xs:enumeration value="preserve"/>
   </xs:restriction>
  </xs:simpleType>
 </xs:attribute>

 <xs:attribute name="base" type="xs:anyURI">
  <xs:annotation>
   <xs:documentation>See http://www.w3.org/TR/xmlbase/ for
                     information about this attribute.</xs:documentation>
  </xs:annotation>
 </xs:attribute>
 
 <xs:attribute name="id" type="xs:ID">
  <xs:annotation>
   <xs:documentation>See http://www.w3.org/TR/xml-id/ for
                     information about this attribute.</xs:documentation>
  </xs:annotation>
 </xs:attribute>

 <xs:attributeGroup name="specialAttrs">
  <xs:attribute ref="xml:base"/>
  <xs:attribute ref="xml:lang"/>
  <xs:attribute ref="xml:space"/>
 </xs:attributeGroup>

</xs:schema>

Index: modules/damieng/graphical_editor/daxe/pubspec.lock
+++ modules/damieng/graphical_editor/daxe/pubspec.lock
# Generated by pub
# See http://pub.dartlang.org/doc/glossary.html#lockfile
packages:
  analyzer:
    description: analyzer
    source: hosted
    version: "0.22.2"
  args:
    description: args
    source: hosted
    version: "0.12.0+1"
  browser:
    description: browser
    source: hosted
    version: "0.10.0+2"
  collection:
    description: collection
    source: hosted
    version: "0.9.4"
  crypto:
    description: crypto
    source: hosted
    version: "0.9.0"
  intl:
    description: intl
    source: hosted
    version: "0.11.5"
  logging:
    description: logging
    source: hosted
    version: "0.9.2"
  meta:
    description: meta
    source: hosted
    version: "0.8.8"
  path:
    description: path
    source: hosted
    version: "1.3.0"
  stack_trace:
    description: stack_trace
    source: hosted
    version: "1.0.2"
  watcher:
    description: watcher
    source: hosted
    version: "0.9.3"

Index: modules/damieng/graphical_editor/daxe/pubspec.yaml
+++ modules/damieng/graphical_editor/daxe/pubspec.yaml
name: daxe
author: Damien Guillaume
description: Dart XML Editor
dependencies:
  browser: any
  crypto: any
  intl: any
  meta: any

Index: modules/damieng/graphical_editor/daxe/.settings/org.eclipse.core.resources.prefs
+++ modules/damieng/graphical_editor/daxe/.settings/org.eclipse.core.resources.prefs
eclipse.preferences.version=1
encoding//lib/LocalStrings_en.properties=UTF-8
encoding//lib/LocalStrings_fr.properties=UTF-8

Index: modules/damieng/graphical_editor/daxe/lib/LocalStrings_en.properties
+++ modules/damieng/graphical_editor/daxe/lib/LocalStrings_en.properties

# daxe
daxe.missing_config = The config parameter is missing in the URL.

# web page
page.new_document = New document

# left panel
left.insert = Insert
left.tree = tree

# dialog buttons
button.Cancel = Cancel
button.OK = OK
button.Close = Close

# attribute dialog
attribute.missing_required = A required attribute is missing.

# unkown element attribute dialog
attribute.add = Add an attribute
attribute.invalid_attribute_name = Invalid attribute name

# source window
source.select_all = Select all

# help dialog
help.parents = Parents
help.children = Children
help.attributes = Attributes
help.element_name = Element name:

# find/replace dialog
find.find_replace = Find/Replace
find.find = Find
find.replace = Replace
find.replace_by = Replace by
find.replace_all = Replace all
find.replace_find = Replace and find
find.next = Next
find.case_sensitive = Case sensitive
find.backwards = Backwards

# undo/redo
undo.undo = Undo
undo.redo = Redo
undo.paste = Paste
undo.insert = Insert
undo.remove = Remove
undo.insert_text = Insert text
undo.remove_text = Remove text
undo.insert_element = Insert element
undo.remove_element = Remove element
undo.attributes = Change attributes

# menus
menu.file = File
menu.save = Save
menu.source = XML Source
menu.validation = Validate
menu.edit = Edit
#menu.cut = Cut
#menu.copy = Copy
#menu.paste = Paste
menu.select_all = Select all

# toolbar
toolbar.remove_styles = Remove styles
toolbar.insert_link = Insert Link
toolbar.remove_link = Remove Link
toolbar.insert_anchor = Insert Anchor
toolbar.font = Font
toolbar.size = Size
toolbar.align_left = Align left
toolbar.align_right = Align right
toolbar.align_center = Center
toolbar.align_justify = Justify
toolbar.rise_list_level = Rise by one list level
toolbar.lower_list_level = Lower by one list level

# contextual menu
contextual.help_about_element = Help about
contextual.edit_attributes = Attributes of
contextual.select_element = Select

# save
save.success = The file has been saved successfully.
save.error = An error occurred while saving the file

# inserts
insert.text_not_allowed = Text is not allowed here.
insert.not_authorized_inside = is not authorized under
insert.not_authorized_here = is not authorized here.

# validation
validation.validation = Validate
validation.no_error = This document is valid !
validation.errors = Validation errors for the following elements :

# tables
table.Table = Table
table.Row = Row
table.Column = Column
table.Cell = Cell
table.header = Header
table.merge_right = Merge with the cell on the right
table.split_x = Split the cell horizontally
table.merge_bottom = Merge with the bottom cell
table.split_y = Split the cell vertically

table.split = Split
table.merge = Merge

# forms
form.text_edition = Text edition

# equations
equation.preview = Preview

# style
style.remove_styles = Retirer les styles
style.apply_style = Apply style

# dnhiddendiv
div.remove = Remove the div

Index: modules/damieng/graphical_editor/daxe/lib/LocalStrings_fr.properties
+++ modules/damieng/graphical_editor/daxe/lib/LocalStrings_fr.properties

# daxe
daxe.missing_config = Il manque le paramètre config dans l'URL.

# web page
page.new_document = Nouveau document

# left panel
left.insert = Insertion
left.tree = Arbre

# dialog buttons
button.Cancel = Annuler
button.OK = OK
button.Close = Fermer

# attribute dialog
attribute.missing_required = Il manque des attributs obligatoires.

# unkown element attribute dialog
attribute.add = Ajouter un attribut
attribute.invalid_attribute_name = Nom d'attribut invalide

# source window
source.select_all = Tout sélectionner

# help dialog
help.parents = Parents
help.children = Enfants
help.attributes = Attributs
help.element_name = Nom de l'élément:

# find/replace dialog
find.find_replace = Rechercher/Remplacer
find.find = Rechercher
find.replace = Remplacer
find.replace_by = Remplacer par
find.replace_all = Tout remplacer
find.replace_find = Remplacer et rechercher
find.next = Suivant
find.case_sensitive = Respecter la casse
find.backwards = En arrière

# undo/redo
undo.undo = Annuler
undo.redo = Rétablir
undo.paste = Coller
undo.insert = Insertion
undo.remove = Suppression
undo.insert_text = Insertion de texte
undo.remove_text = Suppression de texte
undo.insert_element = Insertion d'élément
undo.remove_element = Suppression d'élément
undo.attributes = Changement d'attributs

# menus
menu.file = Fichier
menu.save = Enregistrer
menu.source = Source XML
menu.validation = Validation
menu.edit = Edition
#menu.cut = Couper
#menu.copy = Copier
#menu.paste = Coller
menu.select_all = Tout sélectionner

# toolbar
toolbar.remove_styles = Retirer les styles
toolbar.insert_link = Insérer un lien
toolbar.remove_link = Retirer le lien
toolbar.insert_anchor = Insérer une ancre
toolbar.font = Police
toolbar.size = Taille
toolbar.align_left = Aligner à gauche
toolbar.align_right = Aligner à droite
toolbar.align_center = Centrer
toolbar.align_justify = Justifier
toolbar.rise_list_level = Monter d'un niveau de liste
toolbar.lower_list_level = Descendre d'un niveau de liste

# contextual menu
contextual.help_about_element = Aide sur
contextual.edit_attributes = Attributs de
contextual.select_element = Sélectionner

# save
save.success = Le fichier a bien été enregistré.
save.error = Une erreur s'est produite à l'enregistrement du fichier

# inserts
insert.text_not_allowed = Le texte n'est pas autorisé ici.
insert.not_authorized_inside = n'est pas autorisé sous
insert.not_authorized_here = n'est pas autorisé ici.

# validation
validation.validation = Validation
validation.no_error = Ce document est valide !
validation.errors = Erreurs de validation pour les éléments suivants :

# tables
table.Table = Table
table.Row = Ligne
table.Column = Colonne
table.Cell = Cellule
table.header = Entête
table.merge_right = Fusionner avec la cellule de droite
table.split_x = Diviser la cellule horizontalement
table.merge_bottom = Fusionner avec la cellule du bas
table.split_y = Diviser la cellule verticalement

table.split = Couper
table.merge = Fusionner

# forms
form.text_edition = Edition du texte

# equations
equation.preview = Aperçu

# style
style.remove_styles = Retirer les styles
style.apply_style = Appliquer le style

# dnhiddendiv
div.remove = Enlever la div

Index: modules/damieng/graphical_editor/daxe/lib/daxe.css
+++ modules/damieng/graphical_editor/daxe/lib/daxe.css
body {
  line-height: 1.5;
  color: black;
  background-color: white;
  overflow: hidden;
}

button {
  padding: 2px;
}

#headers {
  position: absolute;
  top: 0em;
  left: 0em;
  right: 0em;
  z-index: 5;
}

#left_panel {
  position: absolute;
  top: 4em;
  bottom: 1.3em;
  left: 0em;
  width: 15em;
  z-index: 1;
  background-color: #F0F0F0;
}

#tab_buttons {
  position: absolute;
  top: 0em;
  left: 0em;
  right: 0em;
  border-bottom: 1px solid #555;
  background-color: #F5F5F5;
  z-index: 3;
}

tab_button {
  position: relative;
  top: 1px;
  display: inline-block;
  vertical-align: bottom;
  border: 1px solid #555;
  padding-left: 4px;
  padding-right: 4px;
  padding-top: 2px;
  padding-bottom: 2px;
  background-color: #F5F5F5;
  border-top-left-radius: 0.4em;
  border-top-right-radius: 0.4em;
  cursor: default;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

tab_button:hover {
  background-color: #DDD;
}

tab_button:focus {
  outline: none;
  box-shadow: inset 0px 0px 1px 1px #50A0FF;
}

tab_button.selected {
  border-bottom: 1px solid #F0F0F0;
  background-color: #F0F0F0;
  color: black;
}

#insert {
  position: absolute;
  top: 2em;
  bottom: 0em;
  left: 0em;
  width: 15em;
  overflow: auto;
  text-align: center;
  background-color: #F0F0F0;
}

#tree {
  position: absolute;
  top: 2em;
  bottom: 0em;
  left: 0em;
  width: 15em;
  overflow: auto;
  background-color: #F0F0F0;
  white-space: pre;
}

tree_div {
  position: relative;
  margin-left: 0.5em;
}

expand_button {
  position: absolute;
  left: -14px;
  top: 0px;
  display: inline-block;
  padding-left: 1px;
  padding-right: 1px;
  margin-right: 2px;
  text-align: center;
  cursor: default;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

expand_button:hover {
  background-color: #DDD;
}

tree_node_title {
  cursor: default;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

tree_node_title:focus {
  outline: none;
  box-shadow: 0px 0px 1px 1px #50A0FF;
}

tree_node_title:hover {
  background-color: #DDD;
}

#insert button.insertb {
  width: 80%
}


div.menubar {
  background-color: #FFFFFF;
  color: #000;
  border-bottom: 1px solid #000;
  cursor: default;
  z-index: 10;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  font-family: sans-serif;
}

div.menu_title {
  position: relative;
  display: inline-block;
  padding-left: 0.5em;
  padding-right: 0.5em;
  font-size: 0.9rem;
}

menu_title .disabled {
  color: #AAA;
}

menu_title:focus {
  outline: none;
  box-shadow: inset 0px 0px 1px 1px #50A0FF;
}

div.dropdown_menu {
  position: absolute;
  left: 0px;
  top: 100%;
  min-width: 5em;
  white-space: nowrap;
  z-index: 10;
  font-family: sans-serif;
  font-size: 0.9rem;
}

table.menu {
  border-spacing: 0px;
  color: #000;
  background-color: #FFFFFF;
  border: 1px solid #000;
  box-shadow: 2px 2px 2px #AAA;
}

table.menu tr td {
  position: relative;
  padding-left: 5px;
  padding-right: 5px;
  cursor: default;
}

table.menu tr.checked td:nth-of-type(2)::after {
  content: "✓";
}

div.submenu {
  position: absolute;
  left: 100%;
  top: 0px;
  width: 20em;
  z-index: 10;
}

disabled {
  color: #A0A0A0;
}


toolbar {
  cursor: default;
  z-index: 5;
  padding: 1px;
  background: linear-gradient(#F5F5F5, #DADADA);
  box-shadow: 2px 2px 2px #AAA;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

toolbar-box {
  display: inline-block;
  line-height: 20px;
  border: 1px solid #AAA;
  margin: 0.2em;
  background: #FAFAFA;
  border-radius: 5px;
  overflow: hidden;
  vertical-align: top;
}

toolbar-menu {
  display: inline-block;
  line-height: 20px;
  border: 1px solid #AAA;
  margin: 0.3em;
  background: #FFFFFF;
  vertical-align: top;
}

toolbar-button {
  display: inline-block;
  line-height: 18px;
  padding: 3px;
}

button-disabled {
  opacity: 0.3;
}

button-selected {
  background-color: #CCC;
}

toolbar-button:hover {
  background-color: #DDD;
}

toolbar-button:focus {
  outline: none;
  box-shadow: inset 0px 0px 1px 1px #50A0FF;
}

toolbar-button:focus img {
}

toolbar-button.button-disabled:hover {
  background-color: transparent;
}

toolbar-button img {
  vertical-align: middle;
}

div#doc1 {
  position: absolute;
  bottom: 1.3em;
  left: 15em;
  right: 0em;
  top: 4em;
  overflow: auto;
}

div#doc2 {
  cursor: text;
  padding-right: 3px;
}

div#path {
  position: fixed;
  left: 0px;
  bottom: 0px;
  width: 100%;
  height: 1.5em;
  z-index: 2;
  border-top: 1px solid #999;
  font-family: sans-serif;
  font-size: 85%;
  background-color: #F0F0F0;
  color: #000000;
}

textarea#tacursor {
  position: absolute;
  width: 0.5em;
  height: 1em;
  border: medium none;
  opacity: 0;
  resize: none;
  pointer-events: none;
  text-indent: -1em;
}

span#caret {
  position: absolute;
  width: 1em;
  height: 1em;
  border-left: 2px solid #555;
  pointer-events: none;
  cursor: text;
  z-index: 5;
}

span#caret.horizontal {
  border-top: 2px solid #555;
  border-left: none;
}

dn {
  word-wrap: break-word;
  white-space: pre-wrap;
}

div.indent {
  margin-left: 1.5em;
}

/* Tag */
span.empty_tag, span.start_tag, span.end_tag, span.simple_type {
  font-family: sans-serif;
  font-weight: normal;
  font-style: normal;
  font-size: 85%;
  text-align: left;
  color: #000000;
  background-color: #FFFFB0;
  border: 1px solid #707070;
  margin-left: 2px;
  margin-right: 2px;
  padding-top: 1px;
  padding-bottom: 1px;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  box-shadow: 1px 1px 1px #A0A0A0;
  cursor: default;
}

span.empty_tag {
  padding-left: 2px;
  padding-right: 2px;
}

span.start_tag {
  border-top-right-radius: 0.6em;
  border-bottom-right-radius: 0.6em;
  padding-left: 2px;
  padding-right: 0.5em;
}

span.end_tag {
  border-top-left-radius: 0.6em;
  border-bottom-left-radius: 0.6em;
  padding-left: 0.5em;
  padding-right: 2px;
}

selected span.empty_tag, .selected span.start_tag, .selected span.end_tag, span.selected.simple_type,
    .selected span.simple_type {
  background-color: #F05030;
  color: #FFFFFF;
}
invalid.selected>span.empty_tag, .invalid.selected>span.start_tag, .invalid.selected>span.end_tag,
    .selected .invalid>span.empty_tag, .selected .invalid>span.start_tag, .selected .invalid>span.end_tag {
  background-color: #F07030;
}

selected {
  background-color: #50A0FF;
  color: #FFFFFF;
}

invalid>span.empty_tag, .invalid>span.start_tag, .invalid>span.end_tag {
  background-color: #FFE0A0;
}

span.start_tag img, span.empty_tag img {
  vertical-align: middle;
}
span.start_tag img:hover, span.empty_tag img:hover {
  background-color: #F0F0B0;
}
selected span.empty_tag img:hover, .selected span.start_tag img:hover,
selected span.end_tag img:hover, span.selected.simple_type img:hover {
  background-color: #F07050;
}

span.long {
  display: inline-block;
  width: 90%;
  line-height: 1.2em;
}

span.attribute_name {
  color: #000090;
}

span.attribute_value {
  color: #005000;
}

/* dialogs */
div.dlg1 {
  position: absolute;
  left: 0px;
  top: 0px;
  width: 100%;
  height: 100%;
  z-index: 10;
  background-color:rgba(127, 127, 127, 0.5);
}

div.dlg2 {
  position: absolute;
  left: 50%;
  height: 100%;
}

div.dlg3 {
  position: relative;
  left: -50%;
  top: 1em;
  max-height: 90%;
  min-width: 250px;
  overflow: auto;
  padding: 1em;
  background-color: #FFFFFF;
  border: solid 1px #202020;
  box-shadow: 2px 2px 2px #A0A0A0;
}

div.dlgtitle {
  text-align: center;
  font-family: sans-serif;
  font-size: 120%;
  margin-bottom: 1em;
}

div.buttons {
  text-align: right;
}

div.buttons button {
  margin: 5px;
}

input.valid {
  color: #006000;
}

input.invalid {
  color: #F00000;
}

input:-webkit-autofill {
  -webkit-box-shadow: 0 0 0px 1000px white inset;
  -webkit-text-fill-color: #006000;
}

required {
  color: #B00000;
}

optional {
  color: #005000;
}

/* help */
button.help {
  line-height: 1em;
  font-size: 80%;
  padding: 2px;
  margin-right: 7px;
}

div.dlg3 table {
  border-spacing: 5px;
}

span.help_element_name {
  font-family: monospace;
}

div.help_regexp {
  font-family: monospace;
  margin-bottom: 1em;
  word-break: break-all;
}

span.help_list_title {
  position: relative;
  margin-right: 2px;
  border-top: 1px solid #505050;
  border-left: 1px solid #505050;
  border-right: 1px solid #505050;
  padding-left: 4px;
  padding-right: 4px;
  padding-top: 2px;
  padding-bottom: 2px;
  background-color: #F0F0F0;
  border-top-left-radius: 0.4em;
  border-top-right-radius: 0.4em;
  cursor: default;
}

span.help_list_title.selected_tab {
  top: 3px;
  z-index: 3;
}

div.help_list_div {
  height: 15em;
  overflow: auto;
  border: 1px solid #505050;
  background-color: #F0F0F0;
}

ul#help_list {
  margin-top: 0px;
}

li.help_selectable:hover {
  cursor: default;
  color: #30A0F0;
}

/* find */
div.find {
  position: absolute;
  bottom: 1.5em;
  left: 15em;
  right: 0em;
  height: 9em;
  overflow: auto;
  background-color: #FFFFFF;
  color: #000000;
  border: 1px solid #505050;
}

div.options label {
  margin-right: 1em;
}

/* source */
div.source_window {
  position: absolute;
  z-index: 5;
  left: 1em;
  top: 2.5em;
  right: 1em;
  bottom: 1em;
  overflow: auto;
  background-color: #FFFFFF;
  border: solid 1px #202020;
  box-shadow: 2px 2px 2px #A0A0A0;
}

div.source_content {
  position: absolute;
  z-index: 10;
  left: 1em;
  top: 1em;
  right: 1em;
  bottom: 3em;
  overflow: auto;
  font-family: monospace;
  word-wrap: break-word;
  white-space: pre-wrap;
  background-color: #FFFFFF;
  border: solid 1px #A0A0A0;
}

div.source_bottom {
  position: absolute;
  z-index: 10;
  left: 1em;
  right: 1em;
  bottom: 0em;
  height: 2em;
  text-align: center;
  background-color: #FFFFFF;
}

span.source_element_name {
  color: #A00000;
}

span.source_attribute_name {
  color: #0000A0;
}

span.source_attribute_value {
  color: #006400;
}

span.source_entity {
  color: #006464;
}

span.source_comment {
  color: #505050;
}

span.source_cdata {
  color: #503000;
}

span.source_pi {
  color: #640064;
}

span.source_doctype {
  color: #646400;
}

/* selection */
selection {
  background-color: #50A0FF;
  color: #FFFFFF;
}

/* DNAnchor */
anchor {
  text-decoration: underline;
  color: #0000EE;
}
anchor img {
  cursor: default;
}

/* DNHiddenP */
hiddenp {
  position: relative;
  min-height: 1.5em;
}
hiddenp::after {
  position: absolute;
  right: 1px;
  bottom: 1px;
  content: " ¶";
  color: #CCC;
  z-index: -1;
}

/* DNList */
ul.list {
  list-style: none;
  min-height: 1em;
  margin-top: 0em;
  margin-bottom: 0em;
  margin-left: 0em;
  padding-left: 1.5em;
}

img.bullet {
  cursor: default;
}

/* DNWList */
ul.wlist>li.selected {
  color: inherit;
}
ul.wlist>li.selected span {
  color: #FFFFFF;
}

/* DNTable */
div.table>table {
  width: 100%;
  border-collapse: collapse;
  border: 1px solid #000000;
  border-spacing: 0px;
  box-shadow: 2px 2px 2px #A0A0A0;
  margin-bottom: 2px;
}

div.table>table>tr>td {
  min-width: 2em;
  border: 1px solid #000000;
  word-break: break-all;
  padding: 2px;
}

form.table_buttons {
  padding: 1px;
  background-color: #E0E0E0;
  border-top: 1px solid #000000;
  border-left: 1px solid #000000;
  border-right: 1px solid #000000;
  box-shadow: 2px 0px 2px #A0A0A0;
  font-family: sans-serif;
  font-weight: normal;
  font-style: normal;
  font-size: medium;
  text-align: left;
  color: black;
}

td.header {
  font-weight: bold;
}

/* DNFile */
img.dn {
  cursor: default;
}

img.dn.selected, .selected img.dn {
  opacity: 0.7;
}

/* DNSpecial */
span.special {
  font-family: STIXSubset-Regular;
}

table.special_dlg {
  font-family: STIXSubset-Regular;
  cursor: default;
}

/* DNForm */
td.shrink {
  white-space: nowrap;
}
expand {
  width: 99%;
}
span.form_title {
  position: relative;
  top: 3px;
  border-top: 1px solid #505050;
  border-left: 1px solid #505050;
  border-right: 1px solid #505050;
  padding-left: 4px;
  padding-right: 4px;
  padding-top: 2px;
  padding-bottom: 2px;
  background-color: #F0F0F0;
  border-top-left-radius: 0.4em;
  border-top-right-radius: 0.4em;
}

selected span.form_title {
  background-color: #50A0FF;
}

div.form {
  font-family: sans-serif;
  font-weight: normal;
  font-style: normal;
  font-size: medium;
  text-align: left;
  color: black;
}

div.form table {
  border: 1px solid #505050;
  border-spacing: 0px;
  box-shadow: 2px 2px 2px #A0A0A0;
  margin-bottom: 2px;
  background-color: #F5F5F5;
  border-top-right-radius: 0.4em;
  border-bottom-right-radius: 0.4em;
  border-bottom-left-radius: 0.4em;
}

selected div.form table,  div.form.selected table {
  background-color: #50A0FF;
}

div.form td {
  padding-left: 3px;
  padding-right: 3px;
}

/* DNFormField */
form_field {
  width: 100%;
  min-height: 1em;
  background-color: white;
  border-top: 2px solid #A0A0A0;
  border-left: 2px solid #A0A0A0;
  border-right: 2px solid #F5F5F5;
  border-bottom: 2px solid #F5F5F5;
}


select.invalid {
  border: 1px solid #F00000;
}

/* DNSimpleType */
span.simple_type {
  padding: 3px;
}

/* Fonts for symbols and equations */

@font-face { 
    font-family: 'STIXSubset-Regular';
    src: url('fonts/STIXSubset-Regular.eot');
    src: local('STIXSubset-Regular'), url('fonts/STIXSubset-Regular.ttf') format('truetype');
}

@font-face { 
    font-family: 'STIXSubset-Bold';
    src: url('fonts/STIXSubset-Bold.eot');
    src: local('STIXSubset-Bold'), url('fonts/STIXSubset-Bold.ttf') format('truetype');
}

@font-face { 
    font-family: 'STIXSubset-Italic';
    src: url('fonts/STIXSubset-Italic.eot');
    src: local('STIXSubset-Italic'), url('fonts/STIXSubset-Italic.ttf') format('truetype');
}

span.symbol {
    font-family: STIXSubset-Regular, "Times New Roman", Times, serif;
}

Index: modules/damieng/graphical_editor/daxe/lib/daxe.dart
+++ modules/damieng/graphical_editor/daxe/lib/daxe.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

/**
 * Daxe - Dart XML Editor
 * The goal of this project is to replace the Jaxe Java applet in WebJaxe, but Daxe
 * could be used to edit XML documents online in any other application.
 * 
 * The URL must have the file and config parameters with the path to the XML file and Jaxe config file.
 * It can also have a save parameter with the path to a server script to save the document.
 */
library daxe;

import 'dart:async';
import 'dart:collection';
//import 'dart:convert';
import 'dart:html' as h;

//import 'package:meta/meta.dart';
//import 'package:js/js.dart' as js;

import 'src/xmldom/xmldom.dart' as x;
import 'src/strings.dart';

import 'src/interface_schema.dart';
import 'src/wxs/wxs.dart' show DaxeWXS, WXSException;
import 'src/simple_schema.dart';
import 'src/nodes/nodes.dart';

part 'src/attribute_dialog.dart';
part 'src/css_map.dart';
part 'src/cursor.dart';
part 'src/daxe_document.dart';
part 'src/daxe_exception.dart';
part 'src/config.dart';
part 'src/daxe_node.dart';
part 'src/daxe_attr.dart';
//part 'src/file_open_dialog.dart';
part 'src/find_dialog.dart';
part 'src/help_dialog.dart';
part 'src/insert_panel.dart';
part 'src/left_panel.dart';
part 'src/locale.dart';
part 'src/menu.dart';
part 'src/menubar.dart';
part 'src/menu_item.dart';
part 'src/node_factory.dart';
part 'src/position.dart';
part 'src/node_offset_position.dart';
part 'src/left_offset_position.dart';
part 'src/right_offset_position.dart';
part 'src/source_window.dart';
part 'src/tag.dart';
part 'src/toolbar.dart';
part 'src/toolbar_item.dart';
part 'src/toolbar_box.dart';
part 'src/toolbar_menu.dart';
part 'src/toolbar_button.dart';
part 'src/toolbar_style_info.dart';
part 'src/tree_item.dart';
part 'src/tree_panel.dart';
part 'src/undoable_edit.dart';
part 'src/unknown_element_dialog.dart';
part 'src/validation_dialog.dart';
part 'src/web_page.dart';


typedef void ActionFunction();

/// The current web page
WebPage page;
/// The current XML document
DaxeDocument doc;
Map<String,ActionFunction> customFunctions = new Map<String,ActionFunction>();

void main() {
  NodeFactory.addCoreDisplayTypes();
  
  Strings.load().then((bool b) {
    doc = new DaxeDocument();
    page = new WebPage();
    
    // check parameters for a config and file to open
    String file = null;
    String config = null;
    String saveURL = null;
    h.Location location = h.window.location;
    String search = location.search;
    if (search.startsWith('?'))
      search = search.substring(1);
    List<String> parameters = search.split('&');
    for (String param in parameters) {
      List<String> lparam = param.split('=');
      if (lparam.length != 2)
        continue;
      if (lparam[0] == 'config')
        config = lparam[1];
      else if (lparam[0] == 'file')
        file = Uri.decodeComponent(lparam[1]);
      else if (lparam[0] == 'save')
        saveURL = lparam[1];
    }
    if (saveURL != null)
      doc.saveURL = saveURL;
    if (config != null && file != null)
      page.openDocument(file, config);
    else if (config != null)
      page.newDocument(config);
    else
      h.window.alert(Strings.get('daxe.missing_config'));
  });
}

/**
 * Adds a custom display type. Two constructors are required to define the display type:
 * 
 * * one to create a new node, with the element reference in the schema as a parameter;
 *   [Config] methods can be used via doc.cfg to obtain useful information with the reference.
 * * another one to create a new Daxe node based on a DOM [x.Node];
 *   it takes the future [DaxeNode] parent as a 2nd parameter.
 */
void addDisplayType(String displayType, ConstructorFromRef cref, ConstructorFromNode cnode) {
  NodeFactory.addDisplayType(displayType, cref, cnode);
}

/**
 * Adds a custom function which can be called by name with a menu defined in the configuration file.
 */
void addCustomFunction(String functionName, ActionFunction fct) {
  customFunctions[functionName] = fct;
}

Index: modules/damieng/graphical_editor/daxe/lib/fonts/STIXSubset-Bold.eot
+++ modules/damieng/graphical_editor/daxe/lib/fonts/STIXSubset-Bold.eot
+J
ó¼Û:’YÞˆ”Ú±(ÚWë5©YiWc¨%%:´-&%òƒÛ¦C_}¯ÔåEs²L¾^¬._’¦:hÚ5$E£x› €ùB߉BÖßžP&,’Doí_ã03K-Ãd4]Ècìÿ€L#™ê’ŠUyÞŸ`K“×™¶¼±IU¥ÍòñMÚ‹ûp¿òâj†åÕ	Š©ˆ‡@uãÚ'‘ž;ê|Ç!yã.#d„’Öto†–(ôØXrÃŽèVÞ¡*³ïÀHU`J€scb†aÊTêùWJðV¸@Áf7,+dLEã£cvýp©¹&ëÌö*Q	Gìf0>6ÆPѾéàƒ¹*\¬²‡…í\BÈâêäƐ'„‚= 0“eVF&®näc”OEÀä²Uc!2×TøžØø~Ec`I<±Ýróè1
ÑI×'ëçâC…m`[6oxâÛ….¨ÿ¥ø_pF?Qý¢p«ÜxFWH0#ز¡‰1çÌD9÷ˆg4 ­×' “13.pØÆDÀ,¶8Õ²‚ÂÈÎ5 I‰*¡\êëz˜s‚;Ë”×qí\÷x“£R.Əlh6³C;4IÖ¬áJ¢=¢=êT™­w?EN“]Ý”
0M–uE©ÛQšam;ÀQ†‘´«u°rÚ-¡A”™)óñ”пؾÕ1žÁÂè=ƒå7{
AtE5éÛ/ºÜÛIˆ´rD&*ÿÿJÿœpƒ7½пâœØ
`Md-=‘)Õ¢7Í~¢e†$‚£ž‘üœžè­õ2R™r”ÈGHr‡¸’ƒÖzûÏKsÒKðVÿ~ç[ŠGÌRó³-]Ž»²c¤êö«à£+{'t(}!@rWU}h3ŠqWPAÉ^
‘TäsÍ”}EÎÉæÛª1žô *t¤ájÇý	kBà‡µrJ*‘$Ž'†BÌÈÔ¥è*ðu.ÛiƒU)6´*1í`1-	y!å槖êÆ#èÒDG’­½€Œt%•òoqŽÄi0lë±^]šªI±dI©mŸ“(Ï·†—™â%†’¹#mÓ:'6Vht‰q°]9樐¦ø#=_J CAr1`hÛTBít`)¬ßr]-åX3*¤€¾0’¢R"Œ
º°––´8'F¨3`ü7@»8…ŠµŸ<»=	©Q@$dJÃÒX|'Äï,NÍ-•Ã	³%j!’PŒpD°ÓH”aŠ¡%ñ×ÖöKÉÇS!äÊr¨ôUt(ïDÚ¬’‚MŸ…6¼-?x¾:é@ð`©¹ƒø‰^¸›%ë‰Û6Úî
P Hp‹ÅÚÂ]µl®!ÎúÇŽ{R„°õ³„À&å`–L@»•l¢m8m¿BjÔò=º¡×ó. ‡ÞÂTšz•ôíªî*ª* ï0ñÑü?>xzåb[}@H9ÏŽSæŠydk¯SV¸çÈž40ÕœLÐèەاˆP¿ŸØ ä¨òÁ1YnãÈY¼h»14deÐ+ÝdؾKM¨!æR´™	/dçói6×e‚'
ôÚÃÿ[[TˆÙÕ-ü²xõ§€†ÁzuO²áBÍ£ÙZÁëÙa¡‰.nò)¯´Z'€å’[B4fxTF§}–	WÚVŽà­µÖró‰î×[ƒò2!þhÕÆ~ˆ!VãF*Àâý9…¿ÆH¹rþá@w):6µiJ”²sp£`K"ÍwžãD£ñß›ï™b:u—rð°”fpô?£4wøÁtÄàˆF76rÜÛ"
²ÙÔ2Éâې
³^CåDÓeóèiøGüfˆÝX4¶­±²fˆa­ÃÌ­	0Q=X£¶·Ä~!ÊÁŸ(4ªÏžK0	t:ÇŽIŽ™òü$·²#‚uL²|X™Y®7ø‚}Í£ñd²Ep¦&“
ª°ÓÏš(ÚÈ
³¤‡H×~§Ç>qvÁd§ü Èõ”IjÍx‘,pM5¿/›‹%ÆeÝ2Ç‹FÅWDÏOÿ”™ÖX~š\‚ˆG8RT¥<¯ã'‰Q	`v@‹JÓâö«¦·Ã2óåJ0Õ;½†_çµæT÷߆e^‘ž_-ˆ ß²‹ÂMz-å*(gÁD²—×£d˜ÿшHt!•µ6,ÌmteA–	-ó« RWzÈJC^M­Â¯pðãßxìX'	jQÛv«TÃÙ£ÙÄã~β~2ÄéÒ%ö>`‰c,ØÕíŽR¢®SDøÞTb—>ÚÂfÊÀEÍ4zzÚxMÕ.eÓ.qPâP7Öñ7GŒÆ‘ï%@Óh*Œ"Ÿ¦
Ù½ŠVö¯ûE nYع뛱f<Æ	Í,ï)pñÑ.ã‹s1¼…cµG¬Fµ`G¨p”mÛ¯»Ðž`‰—,ŽáêqøuÒ0òìhRFcg'£C…­8ŸÞ¡<D¦l…ü-UëÑú ÇÐÓ-Öû8šEc0ê2Ü3=$Ò®_ߦVÀCE€’a%&=àè»—‘†˜ìºþžîù;ûˆp6Øp¶ø­s8‡sì(Œ’¾íÏ2vdиÂ\œõ$Æ6„aßMìj‹­›Hù™a¼ìÎoù] Ϥ0ûEJÀò¼a*%TêAk¨?ë„pe—AYÊB¾=¤ì¯-ÐPjöé¡Õ¸ø	
²«ANÅ2Tùžf‚d’BáT^Î$KÚ4Cé+·:m*ê¨h*÷^zŸXÕG5aË~½º7ÔîABÉÉûñéË +£î	¾M~&x?«¹µÑ‚sfšØÒÂÐY»ç*i;ý­Ï²ÔÞMt¯sÍqŸ|[Z
7”1A:Ü'̘HzNè‹Ë=G(ôp†8uã„\9‡`°ùKE‹
ƒœ@!ð•lbç|[ÜFûøx!6Ôaöú,T(㒼ئ–Z‹@Æč·FÄãYŒ(ù¸P2À^øPô©AhÐasåÐÂÊÁ5­üæKç°c¯‘Ë:h ¨©ÜsV\2§>¯ksOµÕ'èŒ ÈÇ“	Ï@³9a'ýðSH‚
ïÏY¤€hb,>‹Û„?ÅyÇÜÙèïdh—Zó4Ò&Ü ·Yµ›‚%ÖÏÇi‘!m³ghí$>Þ½
Ó!‹{SÕwy主Õø.¹ó šl¹”/ËçI1cA;£œÿÂÓdäa}ê~’ ôi})H_oÑNW4
¶<,`ý͘ìÇñ«g<»Qá£y.NQ³Šž˜¡fŠòËÜLOåîBí¢™D×uKíÔ„#›¤=ós¢Ý4’—€9)náu_潯$kØNÛŒŸC:C	¶HÞO’£ôãûøE²(î³	½cµ-Nú:Ü>VY÷<jÃy¡ÎÞÀý6y!°ipØl]V¢«‡}„œL1H-¤ÉИñ&Œ”CA±³-¬äPØé¥XZµ)ŽÉÖ)u
ÂØKöcM¬D2;€‘b|©WÐ,
ä­º`,‡˜â½yÛar«~i@¶Xkþ"E ¯]/kB’ÓÆ)³ë©G/ŽŸ„Ââ\«›‘ƒEÔÙ­ïÊüÿà/®+?],Ï<À'
H,!‡mù-Í¡xÌXíQɃq°+óe›ÄÙ
Ûx¶á© ºBzæ¯T"Ô‡Ùgs¹–*XHØÕëúLÞaæ1C¹~)Œ›öe|qd±là€¹‰+¥sØbp­5…ðÐZÞH?Î	NÙ‘I°qO~nûm¡)Á²ÕÜ^ÂF“TÕ4¯xkln^šÕÔõå	à£Â„U¹âÒ¡	ì‡ûF%,4ïm“2KfaJ4âµÈ  dÀ׍AQ9ŸNSÜ
Ås¸ÈcS
Ô"ÅéÔb¦@AfY–”(ÎR2’¾	 IRL
ö- ÆxCÁ÷£qç„_½wéJþÿÈR¡±¤}›?Ñj
â:¯¶o°„“k¥bö¹‰:‚8ÐKó¥@tÝ’ÓƆ”Óu"5bâ
Aðô¨¼°%<ñ˜¹<»Ê¬>Ⱥ{ó™NpO¬æûАZ»òÿEL™m—{i½…ÿËQ 8É
NÌgB¢¼rkE›G’ÜÖŽœ!ÇwœT—Zã«‚çʼðS	Ù²ÑsX`S(@[½@Üa4;‚*ùHi`&¨4AMh5ä“Ð7vM¡ƒÈ=„™\°ÒlÁ­Ál[;œøÞ’«)†€6%H$Éöv›Nɨ€p۳눕̃öz–@Öl;"Ýy8Å·/shTZWþrFWR”›^c>\6ö_¦$Xg˜‘"®s«
ÊœMÜ~¶ÏÐ!E­žñÞømw&~’AuëZ¹üÄ8ÇXìIÑ`ÿ½ìC$ï#íH3<påªU$¢æª‰ÀõÏAwTâRHJ"›Ž7xÒXŽšþˆ	2S÷cB}Û8-«Hôt#_)óöa5ˆálrZåñUe¸òbF„?ÉkZ¤4ÈŽ;>Œ*tóaôøÔÊf—M…˜Z>FlFJè
à&z‹àì"ýӍ
ÐO…OÈÑ=&ÏøY.Ìâà<“EÍ°¯Ï㳋Z$¢B¡ÿÖ„Ž*8²Ê9Rïy-4ßéä<a„xN-'”¶ÉæÃaÓàVàjɘâ1áÔc~|*%Éü|l88P‘c'w]1m@)ªZ|{+V_|.Æö45qßÖúñh1â![c
ØÀl%õ4¿@×k]Cäàîç
зµÛÒÄOkJ}hª+žÑ5Å(¿ÂCŸnôŠ=þðPÜ ØŽA,ò&ˆ$ù…ÄÚ—A|/ÿµƒ‹ñó4#Ɓ²Áª\Jþ²GðÁ®w×mî\ÕÔ¨áOÛÏ¡zщ€Ø'/ЯkeþÑ+/æ®5)GÁ	ÂLHÝÉç€Á@bä”HUágMg9¿Xð¢>
Œ ؉ìLA!Ó9¥£”;]B¬x|5qìZãÑn«VèÔjG–~Æxª©[	¢¡À[rê2ÙË{ÞçGºîµ2î·¹ódÑgr‡‡?Á7¬Ž4°¿~ØŽ5-ÑÌiäýNOø©À±Æ µÖʸ3k¸®D„œ‘Ê`Þönîý,C…\&
jÐ0
‡Œ¦¤0Ÿ9.¹4Þ±`¶rýŽÁþsÆSZÇxíÅ1¨›20<W¿*Ú öQ0‡4+c at 6úè5—¡5˜ÍçqÿärO:솠	DÒ	{Á?ªÔ™¶kP0Øu<ƍ8zÇ:g趹ÿ‚D‚©dÃ~žd5ÅöÕ–j'ÒIåì­4ÍŸµ±]¦[|×n16l:¥94@mH§!Jáà„ãÂÞåR0áU¹XV<¥á"z¾œ*š¸cô±7)<.TßV¼‡÷_·U±-úæI\?t€|½`
X]-¥ð¢)Ü_dN¬°ÍS)W/¼2£¿M@
ä灉)u`d–¹f£sp5<ªBóSˆÐfż/À¥Âêà‚Îh/à:® „ø}Os:®ytE„H{ˆu’MôèïLê_pDZgZ³ÓdÅ,Ÿñ‚ß%èÄ$Ƶ’;PûûPýÅ–ôãH’4!¾äõ<qûÒOÿ–ë8 |¸Ò(–‡nûvPDáÐNou©¾¢óy†ƒÌ9fÔs$ZÊVà—T-Î2$ÖKÜŒj}ÛÉbò54/¯¤]ž®§*à€Éø'à6¼°w…Lï+Æ,nx pjŽ!¤a]Ô7>b'\v<^ 5jÎawª*¨L`üHª
=ì(¯í·Ž× ºr¤DRà†_Ö6d«ÃŠ}ðgÈFìÅWÿÌç2àªxb>#¯¢ÝN‘ÿ/®àÞ¿¼§Êc‰©…3¡óÄåMVÕL»¢­ 8cƒgžbuõ°¢ºÛ\³qCPæ20ÕÓÉ£•àî#‘\sÂ×;9È
2•ak¶‚À*ï¶Q,³4±vî"_2 #hDtÍÌèR22#Fw†áù{HV”(¸,	(Ôy†êUØTK^È	6×T#a$÷¼¡.ü8‚Œº3‰U…†I'„'ØPH’߸hkÌ!HÑ@jA”¹ó‰dGóPgƒä†âÔ£1ñ¬7PÀÛc²G<YS U[nj8,TRB<ZÞ²Âi[ˆ"‹j!óH–CŒ–(Ñ#\@C:ç7¦‹IäDŠˆ{n0aÖx†yYO­Þ¼ž×kC€×(dÂŒ
Þìv'óèVÏ?$!ž,à8&ôSX€ßb‚4ÄßÃKñ3)˜ ¼N VHwt§:½Àp—=tc99mœ\`ê¯RmúÐ2"À€ˆÌ¶Ð®9ÄðC#—êW†$
m‰{‹
t$)Iü¤1å$2o‹@<îHF,.1ÌK"”ÒÞÞ.iIÎår6ŽN*G…½	¶Ê!¢ÉvÄÓ’¼›³µ­çu«ÃÚJíï*¾¯[‘V} i¯‚Z“Šåp7ðŒ(†Oºi”´ø .&9ÌCtÓÞ¥XDpÓG5ðäkøêò,.Ÿ29Ê°·\¿:™êÏœ*ðgƒ@þÃòP2œÈ)ÿŒâÌØ[½–
óÞá”$£¨ºä£»í«rTÔo“*m—ÜðŸVÒçØ«eêA¦é4Ù
dÝÔô©å/çØ.xÃBV·y¿ƒ®·ý"%8ånÓò/º&U#(z±6Jð¬üz28Æ«‹	jv!5(¨Ôt‡u«
ÉŒ)¢î*›xþÕ4¬£,(x"Å{5Û<çÐtr‰IÑ'ƒ
SؐÎZXAPv)8\-	ú  X®LðR4,Ô—¯sÐÑØ:MNž¾Á˜­‘HŠ|8	Ë‚p4Œ"²<ùü‰Çh¼óÌ€z#ã,ÔB+^U”JWöæ¹¾&3f
3§ä7"îH­bKPéØ!„ÿÙQû‰rúÃ}ÞÙätòI¬Š¹7H…ò'´I°ÆìŽæ.BÅIšW®)çaåiÜZ:úÃy@…•DôÓ
R=Ùp[Î=h5ÕÎ’a•¶ñY%RB”¸µ§a•­•±uJçb9a´$‡&ì´{:Ò†<Kõ4¼çK·¸ÙöÜ#¶lSi©€ÕÍh	5qo:	6»zr!NÜ&ÄÍFïBb£pÀͤ<i—e(ÚÿQŒLMóÄÿ¥‘Ô¡CÒ!¼tæ2~é{„Òá׆I"¾¢Z¯á ™§ÿ]Kÿ‹4pØn1û³¡Áª_ÕCê¦Gç¿òäÀhO:üNÇÈk\í.òßh²'(h†•õYj͉‡òÿs{Ùj?t/ŽämØ€V±ìlÉÜ–AÇj„W/sÓÏiçD¼{G7?é=2˜ç3÷Øvæ;±ŸPÙƒ¶†ßBÑ'»§Ùuå´ë*ÏÐlâRµ|§·^n5šÛY–…W"[E©¤³G#ñ¾ø?ÂX7ˬÿ6,:¥ÇÒXX¯?JK}œ Xo·ØûN¡¦¼Ê¼1žî²vÃy}  ¬¨
ÉÎ:â|cÖÁ÷WNz©aL¤‚GŠÓ¶ÿËu!4‹mÓ¥,(æ%À%<­ƒ—
Öðp–á§Ò
Ѭ´ä$1Í)Ä&4WP¬
:5„ˆŠÔª½8Ö› G®^lOPlîÍi÷_ËWá,^•ÇudNÈpfƒSŽ¨ÍQӝY…—*Úœ‘Y-:²Í´;6‹U†1£cš8³?bØR·jK¯±4©!iG¤ét(êtŠêNt_eÜu.çq0ÑTæ×ç»y%•f{È ¦E;Öðq­Z"ML{2|-½ð…d>Y‚Îv>èÔróH€ñ–
šØ̦-J^+¼Q!x’Bñ%á§Ë×¼è5Ýák›Äwˆî‘Ü#¶4GjdŽÂ͌ٯ.-\\Z¶°µ]aj²¢Ð’¢ÐŠe $Ë_Ó-zHµá"×DK\-q@µÅÑCg•žT+€Ð•ó@wO\¼á\#—z+M$^"*%8–DÈe&„GFdO1#²Á<c’B–¤À'¯H	vâëW³Î{»ƒž‚âÌ”º—,”­4Cà%=´£ÉTÐíM±
Îr¹­™Z
43'êLuhc7Ò›éÀCя'ov[ˆ%“ñSè@÷+ØôÝ—gäl)˜T*kjHA
(²Pyß´8#$,‹I‡LhÕh¼d^éhTC mPD
®N$Q|[|Æ£H€Tp"A¶	…Úw…¡ta I>ä‚DÀkà¥Lƍ@r!QtäÈ‚êCWÊHߥ#P iLxH6²—çw¤@z” SfëˆJâ–Lix.Meì%z;õ‘8*sNÍ×9šœ^ɪ³ü@
 #žV'®e'@å[…øoIC×
˜¹Ø?h›™Xº]ƒîA•%6Fcù\š¿uJºöýßhq=½*üðy¡¹Íf>h›’m¸­ª6¯Vk1ä%ÿÝ°(ß¹RJÿ3݀ɖ_¯0¨œE·iV5Âñ׬wHø!Åä-pÏÂÄ¿„סyw­ôs½ªØþ¡imþ¦ñ”(q
ª^Ð0yË5®’ ¿×úˆ©__”'¾GA·Vø7 at zò ¨Ý
a,@Ø5™N‚¼–³ '8‚“”åôçw‚êá&i
efVC¢)÷<ÃÉeP‚€u…´<¤™ƒ¼RI'oÖÆÞ¢ÿ¦ìÆM¥§Ø†ày‚Q;‰œ„ãÚ‚^à” êW‡7äC	ÏäIêDjqѱA À즦½ÉqåñÙ­qõÇàÛF¬g\({ÿ8øÞPJÞ¸<kÚæ®à¢b"Ã%ý,ÿv¹ÄÔ&oÛ
nþ²¢U˜÷¶am€¡YTVØÔ}Þg–™¢¶…ùm/b}ÈuD?%oƒÞÅ!ÌÔIãiƒ;)'b݆׉úXiÍÝYÊBÊÂqƒZ€¦k£,ä–«EVk‚lçß{ºFGtƺàpHÍLƒ™]æ(1˜Å/ÖjuüoÑ/«¼î/SI,‰ÄÂkèZÀŠBˆLÕG‡…ÛUCÑm0³KÃ
+j¨Ë}§ÏX›!9êFeâ¡b¶,ÛºVq-~±¬qšÂÂQŽ_ÎñôÖ͘;<–ºý«çåÒfå/:¬°àÈ»úÇà³B
e/G™bOFŒ´l½†”ØÀa“ o‰M NhI¸v0„ì% %ÜGëk÷ÞAƒµ ðˆãÜ1‰¨1hõk£[:h?‰¿‰iá1I´m€†#¡YÞXMÓæì¤Ã-.8UC`%ŏŠªÍºe£ˆò#rï„f-àÀÜaâÖ¬Ôƒ´LNq@Ãؽ.ì:	 ÇÙ{1ñå$ÔM×K¼ÑÉ'AÉì}~©`Ç®[¥ïa÷Ô)Ì#ÎsGH¹@ü‘H³	Ì ÒS X<¨g…z[醼‘Ð,3WüO £8ÛS¥K¾ç»N>mȘx¢(¢á5£×sÇc°à`²
¡‚NX>À$f¡£1È­òtÈÂ|‘˜%¼@6À€!ñ–w5ärfY"§Ëì¶â|ý‚Ã߃TŒ~¯-Y!†ák„šI¾i&žø´`ŸÝþÁ¦®Á€ÇSM°kLöžÀØÓJáhÎô‡Æš#…ã…Ú‘)Ž8ÛÅoq•õØáÚuKš$xÃø%˜¦‚»‚HVÈi‚#…£».W%ƒÂn
 7–ÂdfY/˜Ža¾™igv—kô8#”bR!Ά	ndƒˆØNäðKÜáR;'Žý} Jj÷jVÇì°n¼ªéç4€Ìó—I;è¹i“3J#xÀ„á*ÀóŽ³>¹¯hÇÜÊÈÀÇeŸ†@¬ûßÉÔMùLH!>j¾üÆF÷ÿ‰Œ NÂÏãD¯”x’ƒ[ñdfJ„°—	Ð
¡$¶<³Úhb­‹ ž×tK0B(™k
4˜l£΢5a:¹á“ÉÅ•žÖ¦âyöU·nϹTÂ\pf¢!•GT$§QCð¡° wÆÜæ7­ªbu1Iˆ2Â-²y;i	òJ.݇›h»Ö=²Ü£#î?MÄZ½!á¼Ë9šÑº¢È&ƒvã²9Ò3
Ë€`iÑ—ãý/R5§{5wS7šü]Ù¢™
«„8›ùSl]2ÑŠñ¶€~5t&9«åÓ”C>	«„	ÁüÆZX¨´¨E…¦¡ªCa/RT>wM&gŽip*vnf0îŒkÀ»ò%6`PBljâòšzè¶ÁMÂR;p-ð¦
‚½/O´½<œ¦u@·%¶"Ή@¼"ØŠJ‘òØmzãZ
H¸ËÌpòòÄ:ñ8	f=øD ÜúӁo£²+6Èâ
=}ÉÿßaÌ9Ž`ˆƒ½-†”˜Þ¢ªÒ¥C$¨ûOɍ”´ÀïDºÝmΖEw–ǃQ!k2n).œF*GÝx‰b*%H½ê•ÌËÏK˜b¿ƒ€¯
Ý [_½¾ÇœäLrñŽže[áˆt®ˆxseOM€á±Pâ:‚Ìr&De"ø
6í‰ü³ÌƐ3Àoít
­å —Ü^½À?àž‚Æñõu4SÛ¦i¿,æ´RÀÁÿ?² ñ?€xÿŠup!÷ O›+d”ÅÓ”=Xk€
œjV¶Ã«Îg«ÁøU€2BåÄ„jÃQ_é4†¢'<?¥4àNŽ Ä$¦÷óè3ÿI|-qĤ³öÔTÐ5•å7øXGGsG8Â|ŽÔ+×™v
ccÙ5Ù84áos杇„&¯W)Æ`º©‘{Þò„”V¼¨SܹÁ4pLG‡Ðäsð|D@ØÄ}ßÒ‡R#¦XMœFßP×ó4ƒØç_À©7m…H>†¾êÆ¡@#”ŸÝt}IödÚ–R¤í²êcÂ%õáX€<‡•³›kD;ÛœƒÌ¿80·¯ƒF9‹õãÂbŠü&’“6ô,oÒ+{Ã<0²,ü*†?üäÔ£Þ©ûaf)|¹P¦ÆÈ‡÷úi‰£œQÞN›¶c“TËÊÜÎqx®¶Ûwe݈÷jwÓ«ÁCK˜áã×&‡ûŽ$`šûo$Añ¨IF?ùˆ&5#&$ÉŠ$k~©‡4‹Ú^sŠ2.!ûìÄ¢¦ÓÒö›†¥œƒÛÇò/KTsÔE²WöXy`ø/Gç‚ç9pKXè6ûŸÖ4KÀ8Ñb0R;LžK}á9œÑÒ61ôЇ¿‡~³Ì˜I„uØ¥=§©6ºÕÜ.oU5A[r^ØH5ÔÿïŽèrî/S¬ÛÉ-É$èéu1À–PFÄMb’9Á®ÅÄ›ÔÐ]t1WU‰N$)눜ïåÊ-yÄ[¢Æ(Äqe!o"±+²H6eî¿%)Qhm„Åe²5XCƒa\ÈÄäý#àÌFþ0CþŽýž)“ÉÀp‡Ú€h¤Ž»ŸÖŒa9µ°ˆÉ
QÕCð˜í7Nà^äÕCRœ‰)ÒpX—`¸‘—v'Òàꑸ"s¾WøÞܶÂøÅ–>Ža[LQFš[•Ïé/•!e#—m¾e/X¿Uý;òä‹F[‡G\Ç¡9
Á’´	KŒ¯E4§Šn)iÖæÁ`ÃN@ÅìéµR!®ØZÉVÇ%lVèkÁÜ#.Ú¼]ÕÀM@ª@†ùƒ˜d°ž;OÄ$²åc³ñöëäJÎÄ{ùÀÀÃې²ôü‚;”ös"B4Rôäcf4ÍÕ%’ÔyÕ7Õç(D¤5Å™!°ü&À”#xP
Index: modules/damieng/graphical_editor/daxe/lib/fonts/STIXSubset-Bold.ttf
+++ modules/damieng/graphical_editor/daxe/lib/fonts/STIXSubset-Bold.ttf

Þ	K












 
P
~
Ä`‚¶ö 8tœÜ"DŒ¼ö&fŒ¾øD€Ö,„¾þN|ÆVˆ¶Ô
8jŽþ$b–Ö
-v& KD
DN*Dþ˜7!$47Ú91'äATþ®WBä+*Ø+

 áÄ%+Ø.!/ß×	Kþ†
%9W:D9aYRuf7>ÚU-87,$. 
3]8_v
ø`/H=40(/+^I_j

/# 4J	$"~F8‡gp‰2:x*9:ÁIIW
ôF<:IF;;þÛC%8

	˶
›f}”Þ&'-Dê#,Íþo6þÊv/gv«1
\O0^v

$&¨,e”0 M#88ÅL.1	 ~ä
&w1ù$HD Íþry#!,#&

R5"y
3º¼ 
-€& *Dþ˜7!$4¤Ê91'þ+*Ø+
w—ÆÇœ›ÆÊ”SZ\¬Z]È#"77:A8%3C5*É›œÆÆ›ŸÆ!«þÆŸ š ª

 áÄ%+Ø.!/ß×	Kþ†

 ‚¨:-_Z
Gþz[^jcþz†ehbW
þà`AT:[=E<Z1:#,8ˆ3Y<G :[;SA`
	0"
	)6‚k49QIT+'fŠBf1=cþY7Wq8U"	
	6 >:K
&60
(*5Íþpm 2G65In;`K73PHo
	0'0/&
+<³ø"b‹ÍÉI\&
)$+*Fè
Õ{
‚#þ½2Gq
!IC?hBZ+V')08:XF¥hG*2##9%!Y^a]E!&#B´'	G($+$)E&68ŠW_$$.(	fKAx 	(- "!7
5ÕwUC-!*BcJGa
 ‚
)5Æi
Õ§
	ªû
s provided by Coen Hoffman, Elsevier (retired)


i
 !"#$%&'()*+,-./0123456789:uni0391uni0392uni0393uni0394uni0395uni0396uni0397uni0398uni0399uni039Auni039Buni039Cuni039Duni039Euni039Funi03A0uni03A1uni03A3uni03A4uni03A5uni03A6uni03A7uni03A8uni03A9uni03B1uni03B2uni03B3uni03B4uni03B5uni03B6uni03B7uni03B8uni03B9uni03BAuni03BBuni03BCuni03BDuni03BEuni03BFuni03C0uni03C1uni03C2uni03C3uni03C4uni03C5uni03C6uni03C7uni03C8uni03C9uni03D0uni03D1uni03D2uni03D5uni03D6uni03D8uni03D9uni03DA


ÿÒ


ÿÏ
ÿü
ÿõ
ÿè

ÿô


ÿË
ÿØ
ÿÄ




Index: modules/damieng/graphical_editor/daxe/lib/fonts/STIXSubset-Italic.eot
+++ modules/damieng/graphical_editor/daxe/lib/fonts/STIXSubset-Italic.eot
º
*ávVåhYxæjû·¸Š0(ÌW ¾}¼cíÌ	$±°Së=!U^Z7L!T~é†ÆIù× “þ°±’T–:z&„–=¥Æ’ÙOXÌI at MlGõ‘
H)"ÉH«ª§)¡ydƒ‘<ºàÌô@Ó
ÜkYŒ«"žxD”¥a7zÐÆÛ‚”¡²%AC6Y&Hi+I3ºR6Ž•äw$Ç&’'Á½•eÉV'Ê㺕ƨ Á¯‰Á|VŠ’´+K¡Dòr=W”E™@?PÀ98VãG¸ÚXŒÀ18²â×
*h–EbíŒdéǬq²5F+.¤V^QÒ£@Ս•î+ÑV^’rö«/CY{W— ü½ëeê3Nƒ˜QŽ”w=Œ¼%åáŠê†Š!RãѾ£Ò„(!Á0ŠŒ¬6Ãm¬w	T"¡ÅîcnR“vUF
‘Ü-ÖD-]£]FÈW]FVS®F=é=ú¸;‰¥nƒî”uÆ ªÂÐ2Z°=¡í/An¨hÆ6[0˜:ƒF<Œ¾ÌNñdÞcIhòËëÆùÉP>)·`G°¬Áv(TjðSD¶Žbæ$ËΟÞ,ò³ÁÆ…ãЯˆÒñ”ÒòíåKâäŒ?µ T¶éÃõ{é+Š7ÒW¾…QÐQ„Â!œ_;#øT-¥Ȝõ"ç÷è‡õ	ÂG
è šHw¾×$5hDG'^øP×iàzL.º–X™}|Àõ/ë©c†(Ð'8“ÈER':¦ò|\
!À¸ä‡póÓ9f8,Ãt¹C¼”Ž‘›ÞÅúsü%V„¦ÈºÕª…òûFÀ©¿Ì0°}¨aSÀ”ŸVW‚vFd„‘ýlghíO*ñ­ÏêöÕå£Ç°÷%jÔþ08ÔÒE”Þ÷¢Ê†Õ¿…Ï«AFdvˆKM¨A¢fir¯b!ÄŒdd"”c=ÇãÌ4#
Ï7P¡†E¶Þþ1Sî.N…ƒ‡Ãæ^»8´¾‚uAN~\fzµRTB™ê˜ð °63êq)E¦&”-ñ“y¡oÓc{31›ö÷™·9¶±éäÕ‸l‡;éåwØ5Œj¯üM«!7)…#,x¯§¾>¾YºqR¢öÙ‰¦#Þ^ÈóÇÙ
÷Ψ¢ÆlQb$‰òƒü‘ü˜uÓËEžÌg¦Ü7©ŒÚ(¨c8%‰3“2§Û|?<e,7QqO¼g
Q´4"¡JQÑv—³QLš{ŒvOqµ‘ÉVQ€US&áÊ°J2’ I>†ò73
4ílKhœžFW_XHO‰+¡¯CQX®*Ær¹¤°f”·í*{sX(Ûˆ¹’,¥ã‡Ó,Wƒ}2rÓ7-zõ³ôžˆ<³ò'p03²JM1¢p}""Îvs7„t¦A2fÍ…M€“%5äÇA3¡Äô‚DSâÈRbÊ7ËÚ§Ãídg¹N2ÃÓ’\¡3»ºPxÉV—@±°q¶àÈ®®£ò³™êR»O¯êËt„åkSŸü$3éa熚sBÙXÄG
éº`iù8¡Í ÍtÄÅTwk|•ÁÕ¿–b¢$–¾Y
ê)f¸
éki»Y+-˜{p2ùê8õa'í5C§â>%` ÉXŁÜûÎ~Ï­^Ê&±:áÖè‚
²È“‹+û‹Éà©ÿõ1Yîqó~ϱ{²/Fê¦÷{ƒQPìé75?y;í¥A#Íš߃Z¼xœ–ãH"e#$¯†/äÃ0<ëK3h¥——VO—raFd·,çðҧ(Mcƒ"ÃüÉíKÌkCNn s?çnZÄ›wï…ÿ8öùþ^_,E®A£å¾ò*k†Ôí;€5¼ç?¼&€÷y€ jË]=8¸×	gsàÉNLÖS¯<1±C]Zæ,-!Ý@‰è܁ÊKcŠá:  æÃ߬gÒ^&Œ€·.îÅéT‰Âò€¥é›˜~Ö’'ïÁLÜÿ /‡àÃxFå×%­¬MþÓãñþÏàà0eƒõ÷ ^‡À¹
xœþÆpà.0@‚5ºAÈF›N½ŽÉ;q¨uWV.­ w°OÄ-ÀÆ&à9—õ;Ü)»ÖUqa«™Ý7È5šÏÜæëìXUpÔ•»˜#ëëŠ+núÓ‚Ö„”א'÷´ã°ZqV\2±­­FqíÜéÅPm_Þl¬9%®Ê‚ÿJëÉS¯Ïˆ*ÌëëèˆEÕãñ'¢kw>¾|PA4Ò Dç
ã
ÔÝ(qÐso“j¾ˆÒK.u=€6÷ðxh°‘ÿrš·òdõ)¢£¼úOe@Ó©¢¨”ˆ¸I¶·Ô`|»ÒœètRcÅáÕ?\/%)˜Ô÷.¥$…‹TXŽ»µ7`	¨	ÜFÃSÄŸ ðü w¾»Y5:LÛCNÅá’zFl6êžù`–½ÖäsA+
W8󢞌½»õŸôÑ‹¢ƒ‘ÙG<þSëa8ʡÞ£Ö?]Nš3’-"[þ-åe¨COì„\æ‚"˜<}&f¢a§kvÚªÍïéæ„“Ž›8qðR8FÒ)pûÛ€ˆuà'fûÛ8d›á"0áj¤A ‘,d I/ôl|–m]>õÄ5MÖ‘CÊ…¤>š'¤iå±k§¸‹Š">ÍÜœûEÉlÜÈð,[4²áõu~±ë‡IH3ƒG at VéhOñ†»Ôæ;Qq³=·ë×ôÀŒØbôúÖ"Û6€éÉxItߎ&ÙÌä™Xh6Æ€û`íϸVÕ2°À>ÚHÓðõBL®HPó¶ƒk A„.GÍh&ìêðî:å[ϐ´ˆº†¢iŸiž¹pôŸC¾··ƒZ|vè‡õõ[5Éû‡¤þ•CȲš˜.FÜT‡ñrEóõµ'€èdÁoÁ34Öü¯ídk
lOk{ÂëiVÉe+ñ<ȆTÄ!D¤A³4ªx‡>´,
b­á3oì:–ç¹÷ð¸Æ^‘¥Á1U¾br;à
e
àNBæÀ`¥Y‰²~ÄðãÚŒñ?‹4E™—Ž–¸¶oq)àŸI}…4Â5yñš+š«H(¯
-hŠ¬52>à°g§ºèë£dŽàÏGzƒ?å¸ÓDR‚ŒÙªÙ™‰dÙø¢I!n u`Ò
‡Òéy]ŠÖÀÒVšW“¿…ì´2/-šïa¶¬¤ÄÑañàÙ|T‘!ð\£VŠßƒ–öà"ýÁ&+jý`ü’VËÐö@2ö±÷¤9S D~¹_PAÓÓv7X™™öÏ“R$Í^ªæ­a%œ'Ñ-)¯²!x&~óäÙíML„µ+ÀÀ,v Êé»çâÌÝ\uy¢¬—ëã³DÿÆÖQ®ñ±¶	ê㲸Ÿ_»Ai)ƒA±L‰ìCŠtÕG¤ðJó7¨4>IQۜψ«2ʨðŒGˆešG§ªòDˆÊ3U C†Ú1d·òf‚ääà8<¦Ö3tÈ90¹W˜sâ¨GûÆ€ýåýÆA¨ªµ’:ѵlcŠ=4<ÁCå[áÖÛ¨­}[,/Â'>¥`¾Ùœ1o•L«Ž!nÚ†\¨%¶V¥¥6½‡ù™]¿Â?Œé$;RKÕTÑèaþ^Ä?¢c2ù^àz8#î·§;e+ü##~‘6Õ›ó>I¯å ¤RÐé>ú¸äÍy‘C²},°zO
ÁJÖ­+9|Æ3`ßX*W¶–¥±±Y/ëÑݾbêr
Su¼Ëæ–ÁäTm¥EåÁÞ§në)7ˆn"ËT=—v1N”„ô
˜Xl””H¼§”Àm¦€ÅM©¥Ô¯J)ÑQgWÖL`B˜“§æ?ë¯r¢|‡ÿ(%²RñNª_‚«¼–Np®FB€ÓÇaTÈ%Q)×WÂã}…ÀÔbhL[½[ÿ——’Ì"0–6vW¨[vy<Ñ‚È•}aÿ‘‘”Çi?ª¿¼ù˜²ÇO~qoÁKAoEhfMÐ’ÚÕïÛ?hRHŠOú¬3ú¬‹™¿MŽÏ\‚ ò6 ŠÞÆVŽõ·cêø!]‚À§?§öeÄäS7F²ÈÛ‡ÅÄÒø›ñ‹S™+e#R·ù’®W†Áô™óÅrŒÂ }_ï)ZÁWLÂ9*Šó`#aša!j0º×ïòÀæMjÅFãÂ<äøt¾Šd2([–ûö ÌéÚñ’w`»î‰]ÿHËhD‰£˜à|g1„Ï„ùÛ$hU™ëª|ùÞB ¼	 ]É™q$,”f~7WÄL§U@ñ+‹>åY÷¾]%~wä	¥©HÖìØí;58!“1bñÏFBž~…\s¢äxa5F¡J6›d).	ò÷½'šˆ\ZZ—â¾$tÎŒei³
—£Ì¹ç@@B*@tâ&ŸÐȈ)Fïªðωf(z¤i½·%£°š±Ñ(6ЀGüúŠÍÂz0çAÍýRò@§Ô††#D$ì.> ´gAÀÚðuÅ€:»Å“$¢å¤OÏé⛕Ç—ôS8§uN0ó§
}’#jؐª K×WmCu^ô‚ý˜4&¡>i#¯@ªž`‡Ð<7D¼*÷o³Qa¸µì?>C)\0ßý{üIµ¢ûäÆÂ9Ét	Ô€ñ€iȦ|uOˆÏzË— ¥Ã*߃%Œ£hŠEÂ/„!Àã"¬. "Õvd¯Ûëºõ%S
¿ú?Šò¨˺4»Å§½®Ø)!M'WR¼
ÊŽ¼ùI=l˜/ÚŽv¥$”•:i'/õ”Ò'òJÀP¯®Ðrpàō ÔšoEÖA½ÿ¬(U™Ã¤}òøp=É5ËuÇDõÎ[‰ûþ3˜¿5i½YÄŠP¸@lÌ»ëØÔ¦“T·Ù(ª\AVdÉB¤õ—ÊåÁ>.·„ ñ¯ÑcL_€ÃýIÎLxß&œ¾O…Š¨¨ûè ãþCÃDÆõBP•P³¡p„`)l1TÅ&Lúс›áMþòÐà¡G64	ÞúªUúè7N1Íç/«GÙìʲßñ­¦ÐhÇÆÕ_ŒÀÇ-Al;˜§Æ-Ž»†½Fgž4NATÁï¹±Q­dcîk$j`æ¯(ª§/J¥K	>!VˆáÉÐÃ)™»q‚Œ:#cAxX‡"+ŸŽ€.ÆeTiή…ꤡT§¯8Çà8
™MÆ,¨‰„ÍFʲá'Vø^²sòAb۝ÏmuöªŒf®¯¡»
lªˆ·¹ÌŒg2ˆÇìÑ®KÀFéÍT˜z5™÷æjó¢xýuø›héeÓ,9ðÁ¦Vw²^¥@èDöD)`—bw8ŽØëâR°È…¸ûÏáRÜCf	ꊐ§n…<Z.šæ‚qÁÀçjùêîw\ÃU8 
âZ;=™Â%.Ù®Ì)¨C‰Žk1‡ÜðÃȹ$E¨v=Õ	:¶P«9Â2eÀ©‡ê/´Í›ÏÄ|ÝïÆk°gbmk.â¡A’«Ë_§ÅB´%‡_	¹ÌÔ([¥ˆŽ^©Óÿ­8º78»Ì,tï(cVþÈ„I*¸>ZBrJïÅ»wÛ0¤'7ÀIÜ뤌[̨{åMBWÍBÌ“S/qä2ØÑ.jUžý®4w`’»‡lG…â.iÌ÷5Ô(7-Ñ"’v¢êÝ(­)L¡†.6]é²Wþ8)eáÁÜf0jEP[t:
‘¬V‚ôˆÕ¼ŠJQ-ãÎف¨ïêRÁB!ÃM·Rêƒ"Ü’ü=@(ƒùmOÜl£!mç×Éù¦W€Õµ«Yš@¼ºËœÍ-RÐâб³ƒùeàYùCz¨v¾&Y³úò,„.±ÉlU	ý±	/1¹³`1j³Ï£¡§"ž@Ô¿Õç¸ü¶Z>Ñ«zHeÅ»¯Ä`´Y´µu[Ui:¦ÑVZ€±…37OËPµH¨à€|
ÆI	T©rŸ€¾iÖ¨èžK)BkW1¤›"øqjç׸í¡”%žni€ÍÐ@=±(PÊWšˆlÁL÷´™‡Ku0ýj8úÄÊû&ݳTiÎb?n“D›q®°æl98ׇ(±BÇPÊœ/ö-î0£è¤Çüù˜òÏzƒ^Y£˜MþæËà$wt’$Ñvê¥ÏL¾³Îˆ)=մ㟱þ
ÊnÊJÐɽUÄ̐WqRüU³~þï%È£®1„’Î’ÈCã_š‚po¡5L†þ¾JTgPíh*FMfçMr²¼*­~øÿ
˜Þº»ôvº¸ðò •ify4Kt%"Õºv[¼óĤ$züùòy0ðÊËŽYä&fÅñ ô”;§—Ì» ãpjohÞÜpQd/Íôiùœ¾GŸ5kœ
i¡Ð›ó ^‘v
òÒ“Pž«¼QGÔ¸Ž9|TùzçÔà<ˆÒѸ –t	uƒé$áG Ôœ¡Läà5Bñµ®€­.+þÊÒ%ýãÞ*>Ä;À«d—n().2$eûF“ý±]ðxin¾,µ(™´.Z*¨ôR+
§cÄô¶~¸|D§
6z\"E=8£”cÌå¹&<‰~” z‹ê:†kk_O³¸—Eä@r5X²y®¹ðÈHKeÑ4XB|NqŽ®õUp%ý=XH—gH1Áq=çvÛDá˜&f¬ï÷êÚÅÀäX3N0rªr$NÀn{«¦J[”ÛH×â$,Y$eWT¬l#bÊÊÕëT<U›³xh™oØé|^ê`”R«B¥øᑧ¿µ³ŠN‚Z?	ÛéÂä3‚·Ë¶ó&¯\ې~Ç´BæØ*mŸl×üö@jþTäaãæáQƼ.åJÖ>ßÆn
áÙÅáÓá¶Ü1âŒÄò0,qägU–œW¥íÁœ›OüúË4‡p'Yd[È}u}
¡Ó-¨99ª +¾Ðqfa¤ßÁÿ9æ­$Û‡àþHJqr2ÆMƒâÂb´´b̃<=C{S¹Ã‹®a)8N%uýPÀö¿w8àö'¬®•	çØì`Ù©¦\l@ë±þï0Œ1Ú_s@Ðõl57“­¦(ç	u^!›‰Á‚›Ã K=O­^qÇ9 d>¥âQH´Ã·Ö…ã‹þ¸F€¤†]Õ'Ay€á:4ç*´£5=:0]]jïf^?‰T궴¨ý>A#À¤µ³ÆzÀ„ë_8õ` à¢8Îe at OîŽØÁfo|Øoè3@—Á}¦ǶJP¼ÆKjåé<ÓCö-t,ˆ
Hwv„ûÎPM ŠÇ»g&,M´:M˜k]³ÞT
œ°ˆ¯EZ¨ÆìòcÖùM7M‚jBy‘»qq£Âø‹hÝ ¸º\¼
sr¤r}Ö¥ÕP2%­y§m–”eÄE¥•åÜ|¢Z€;`\ü½¯âzøSÄðø³¼ºžGz%Û€0O/
w¦Ö”Ô-vö郞Œ°Ùˆn™&&UoN L­6_^/P-(—¦—qbÁÄpª8û\øš-d~rQA0Á-±¾l0ÆaŒiP”äoÉø«žzñª¦ýæ¯E 3 ›ùz¼½“Hõ–eèÑ&/Y5‹|’FLæændQ0¼«RÁh¯wÔò†§¨^¼±•ïÞko7& è
H¢TéJ~f/þ,ñ¹'nÓ;&ÃéTQHôjOÒSòÖ²g0—‰¤}&“ƉO]Ž¿ÏÙ&Ô ‚rÙXvá·SvÊ?[)%ƒn¥[³—ëG"”ñ}â;ÉîsõIù°;ȲbI„uÚÎeÈj¸éΙ¹F»àÈV„·Z¢«®,ß –£À?‰ù—+ÿ±ÿò@?ªÙb~ÓÐ.ÑŠ§;¥¬øÛ&>f´¦Äàÿ“
␧³uËœÔ6âz:þÊlӍ&[µÂhôƒZˆ>¨²®©‚iÅWKUX“\C–u)³pHŸ¦ô1Ã#¾²AÅdÝW®É:½ØX+k'åÔr™Ö•\Júú3yKé{Ñt¿©éü?–T­Ä£¾~&.-FDÒmOÏ÷â5ž^ŒÔ´a‹Ç!¯»â?	´¡w‚ª&RqŠ´Ú+šÿõ¶î³
=¯aQ«,véy/¶*ÂÓYÈï‡`‘Åoø`×°X/ÌQoòª[d at ZjæÊ€:ìŽB¯·g«^$±OTô'¦×¹J‰VÃ6Œ€Õ VTMz\È€R{޶ܹœQÀM[<„׋Ì!€MCqQá9
—û˺™¼€¤–¹BEè”F}dZ¦UˆãõÐvÑ„¡]úAÐ.Þñ Xi•b^
xüz{uÒÕ¿œ´å:ZÕñ€VÝ	œ@+hýv3^…nðdµ¯Ý*¨X™¡&0ùüIµêy6À”ଘ5»<mI<r“¡7÷¼ù0DVø´+™çdÜ€Û½Ê÷Ñs£`ì0˜=Æwù¢’‘Š›=pa l`Ô=ƒÕnžgV;q.;¥‚=̆ºŠ¨rÉaóÃTÔB÷§
ŒNô`£pTh&C1x¡w‚"ÑüæPÖ,5àŽ×f$‰ˆ‚Ó^·¢­Õ‚ÖÑoc`Xk¬õ×%+uÚ‘j¨g(gP0sMüª-†KˆµYpzÐh™Ú™¦«˜d™p#´OýK°çÁúZ‚à‘“¤´¹Ó÷Wç*zì0¡,5b~wfßð
r,QZœ8¸ÓÆϱköö–)|û&@XL8¹_pAÁá:ÜÀÞ‘¡¨9ʼnÃÄл3ÓJ‘ uд+Dã¦+uR¢	­Ñ—?¢LŒq±‚$£jFÐV™"FK‹tàâh+M8ì
ÙhȲ¤æ¾Àr„!òë¢ifƈ„y+7¥`S²RJ9ðR¡8`$î÷"`®î\Ñü5-]ÇlF0‰£R+ÑOJ‹¤p´‘ ?ŽXKÁùá:nŽõé¡f2/~)Ñ"è‹<‡>è'%³O$[0ŸD§žµ3ú<ñà!ÙéYk@‡';p‚(¡`®»ƍÛ÷Ó‹j2-8~°“‹£bÞhÒ#ÊoÒ@Á?WBÊùô'¦ÓÌÖ~ùBX~š iœÖ]<4óSÁ–'ÿÞð±}“ˆp­vüsRqšõÇõ^•2sâ(hw‹©‡/ÔÍêÖò0̽m¡Œ€·Š6$=O\Ù~ÖªëC
Šì ù&ñ!XõØ-)™upR2yÈäk?ÖÙ	'»5t1Hã`µJÝ1ˆ)ÿòP‹ƒå\	Uh‰H†J(þœL"Á|™aÛáðàU\Ã}åώö±¤±sðìg¬Äãæ…[%8€Ë.™‚–j/WµâI/ˆ‚9$lÓ_ÛŒЍŸ]…'ØAÚû¨ÝÎÅÇp€¢¬V°k+lE8}¯¡»í¶¡âêØ:bΣx#2І€m0òÓ†5ST²8Œ%S1k
P÷VNŸ×G$
"cØÊ&N•h’ôr€;e‡{â"åH;žR‘P‘W!ÂFõ8¼\ŸlßÚâŠl éðm©‹IŠKK(ü«è’/%‹pM	yÆ9½9³zò+ƒp>u؍7©nù±F´Ã¹øpI7Á9œ?m{'ÜõênùNN 7è&c„ýd0ìMÁÒÁÑ’úŠkU:`ìIëtï5"¯TꎉËs½0,´Öõµ,„ÏR	ÐXŸð6È®ëÍŠPkqV(Ñ&–`^˜Î~ȬÀ!~±íä62ÝTaG¨ºÍxóC£nI:5”¬¤ŒA~ @AÈäh˜ãÿžªœxÄcñZ@Ž[OK[“9fÊö´HFZ6äMN¡ÀQÆŽð„sI4
ª¦·µPùtåpz«l˜`0S¾9¦åÄg4+ó[>
¯õbãmt“-Ð&ÕϬüd?Ÿ:?Zz°æ3,+®JmwkËY^YŠŽVûÕâ/zS–­	C
³aU:j{„¯DQ"yÃê$?ãÚK?ccS˜#‡ÍoŸp(1)д#ÃI:‡âvŸGQüïœ'´Çmm ¬ØL«ååœÏÁÀJVéœ>˜æ)(„NU!îv¨
!û^CYõÁÊo{á[è±Ëøn%&ïUÕÎÔ
V’…%Æñ€¯²’®g{Ð*›XÕ~¢p°¨å\üÄUÞé-ä?Q§.
UŸ¡8¼/¸<l¸h5;*áѤc着Û®C’Ùl–).Mú¤&æsÃBá‡MŸ—t¢OµRõq-ÈÎÓu$!“ÓÙ‹ü±!ü¹×öC3,ˆÅ‰|·0
ŒŸ­PÛ䍫ÈÌÖ%þ6y›H¸úŒ²©T†œp,«u3q£(èûÓEÚX‚émb¼,A‰kp &œ¹6j0Á¥Â$—Fª§ÇÒÆè‘HÍtUÒ¤XñœŽa¼07€šÊ%â~·æëD[›!~H4 .ØORDýC×ëÇvžý­”EÁ{ÕÉíhOºa¼y£Üm®mjœA¾Æ
¯üÈOÃå&N
?Ò<W;ËaÉ>	Ç~–/š/ŸÇöü>Á4È¡¹¡¤j’~¼ y¹p1ÞPÏR(§ÌçO³¹Œ»#Å_ F–ƒ#Îújˆ	J.´È‹±=A'–˜Y^ž¡q‡‘q1Øj(
ò1}nI*ÒˆØ,dYP+ª%ãØØ=k«¬ü 44àìug¤%¶Ø˜À±ÅV¬jŒ—Rk;RŠmäxˆ‡#¸}Î,R7³â+?Ñß’(.ß¼

6€FIŠ­ž‚ek˜ðE¢½AìžÛŸÅLª±ýf]‘q9ßø‚.î÷ƒ^Ó!·8v…ø'Æþ#»ÿÎEK
^!ôëP‡ÓºO·ß‹Bn½ÀøŠ²ðŒ®&í¥ÔÚKÀüÙ^èY!Ž°¹vIO¢G‰Þ^õˆ’4
#ìÁË€r?ßf¤‰C!$}‡ð’°ï2q3`Y9¤bÄ(h*»%ø4—:öÌ歁Ánþû@:>D°´œaˆá³>z™Áw¤&$×µSU]’é’_–­åÒ÷‰_RüÁ}†`ñXÍ
Øè%œÝTöùáJˆH	¼”ðXïבé…mjLC¡Ç:¯¿ÉºÕvô ¤ŽÌ
‘r 17Þ<»¹a™pkºA*?%š²~D*àË°	©¨Þƒ!š?¨Ø¿’„IHMKÍ`7WoáË…pߨêˆô¦’`ršf¹µkƒ–ü¿]5(âƒhw<ØWù“1,1ÎR×íü¥1ã(ÿdѵú_iG$÷˜¢4›r^Â¥ˆ Uu ë2	Ù°/Ñ0¿orýšÃÑ$I4¥1†·Á&SDËÞ¥þZ¤9yY`>´iifág‚$ç&PÜrÚ¡1s „æ_û	Rg(ó·zx°yí³t>†â@V³Åâ[þ«f?ä£FÌ«”Œéø¸k°êÒ7G~ †DéD’'89¬´ßiS@”E|“êŽèÈ3ë}ß>œÄ*B$áyàæŒÅZ7F)œ²TBä)õ”³yë3’áøD&ðNÖ#yÆ8ÞùØÉ"O
ˆ˜a^µâ4`¬!â&9.r
¡Ë×÷[¢«ûœ¥fšà‘cnÜ#ÝÈ"-hË÷š›ÕŠ}Dî2`ZœÈÒ
Y° Ö¶
ÂâBàb§Ë4/Ë$SfkP€õ/ƒcêVˆ8²{aÃÙ4Q¹¿LºH·žՐ¡€M7ÄCZ
Y´¾S/VÊZ±ðѝ©Æ´ód>nMCŽX-:ò¿ü¬I5r=’¥§by}Ò+B‹ã¼V·p
 R@'=8º’•S…^zˆ´¶º´¬kzb¹˜XÉ)Bðrê2á…Š§½j·,¸$lR¦‹Ôýk²nóh;=åK“
©tˆw¦ëD׬I2
N®,€™ÎéŒ
ð’|	*A÷»¶•+~ç»lu7œKÙä3`)'É-–Š‹جzë«	 tß,Ù¬ÚɈ(°åê4¢Ëâ!ø¨|Ï$øà ª÷æŒé¹‘fÎlØù:½Š7'Û­×9'& µš,¡éÛž&€GöBÑ	pÚél=@MK¸^é°ªwIõõèQ)Ø(#yŽ£%Äl¾ÿ“lUëdMt_)\­Xœ­u¨+ØmnÔGGõÑgÐÇNVãÖñ9	<À0Ó@2;€¤X«V³‰\4¾x;‘|4« ’‡måvv¼³<yVVs”Ÿ‡T•„W×Ñ2ÄË·ƒÆ‘wé;KfŽÞ
¡ÍÛÅ9Rbà34_ÉzªJ£PŸ£J.˜@s7ðƒ*’IzöÜáfæþ4C˜Xáõ¥X
x)úØGòç£Èô8SÜ p´ÉëÖ#A?¶
¬Ä"Ã5¬ræ(ÿ5Ãv	¡i~š–̪a†(»±D…QMß¶˜nÞ¨o­ÊE?NËdÊþfDöÊè P¥®§Àævzdê{¤8ŒTg˜ýKa2|	ÓSZ/'Dˆ’·!ýð“¸0$ãØušûÛÂ7äOó"¤ü°Œo at 8Äg¿Œ°§$
Ž,WÙ¥é°êäÜàV‚Ô;²%ÅÁAT¹|ãSœœŽl&'uè±¢’È:OHõˆÌ†Øµ®!t)ˆÄ‹Ö@¢l¦¥øiãŒfœba[¬äû5P½˜*Q«
`ÕxQ®·yÜÌáKÌ_×°ÜÏ:3ž¶2q±€ñà˜6%j÷Ä‹fžÃúák¼Â¿;×}n+«%YÐ0ýöÝÍ®î­õéIrJÒúâóT”N¤Û§‚ƒçÏ ûØò¬Ô¹î!íÑ.Üb“%Ž{eáxɽŽ/`.ÜÅwJ–n´î5¹†Àæ"3»Þ9{.0ñŸŽùÐ?3d§sŸÄàí˜ü­.E§_‚þ7ô—´1„˜HPû"ªã::¢¡3‹‹3ûêlåøõÂ-/@‹µBm~ENa‹Ž²ü…öp9‹?ŸI«¯êP»ÙŽñ+¸ƒ?‚ñ<QäWÒ묌»Gl­:#y"*N!£%¡¶g\q65Ÿ_[PBc€‚½ŽF®\ñJùA
ŠÉÏ°ï“
—°Åû†¡ –wÛ´jIq¨¸^ÈÝQ¡þb&‹ænk¼;£”kÊ¥_µò„‹¶jÌ ˜Y›„šE]?¥¶¢ŠGí_fqÂú­Yîä'« ³ã-·;
šAÂu	ù	ŽÑß϶{3|H¾ƒ©=ƒì[n£ðbžôhÉ8E‘^'‰ÄšG<|`P2úfû,¯ïýܾ½e°»³SÈ.[½Tb6FdpLn³ð'[jæ&À³Â7"ÁöÛõ‡]„‰Æ6*áé²`Úì<@¿ñCØç>|ÀiÊksk–*üj·㻓J§>¸k‰»+ pä@ÆÀø´¡ºF‘-ï*)X^¬d@ª‚¡ýV<ãCðiUÍÌ­s¹U
fÜÚÇ	0,iV¯GcLHy!HáÒ
9ʁK¡î-“zo¦EbÃÍ Sã“IÐV$?{x=ý]c'Ó@̆·û›ý"qop‹êV~`΂O"7×–%’rÙ4íÍH×Çh†Ñ!‘Ø‚î?čö§WHʆÖè‘8ž@cý	§1ZˆaßIŒâ¥däÈMü
 _³Ndõï«Dc¦Ò–l¢É7 at Kñ7<ˆgÄ>ÿE'Ü=Ò`—Í÷È\ºô_œ‘^Ê3Še•ýøÉQ™¡:¢*ƒX‚
%S|±˜Np;?$B"$.v[‚ÌŽƒë8¿ßÑôüñid~ º òe5œØømHÆoA¹X›bh!K•÷Õ¤: ‰ _K
ò$Ììs¯‡uT¦ª”äïb;­ÌÈ„Øb½ŠAG>uÿ‚Èì¡,£MÀhŽÀâ£9ogÍ[íDàíŒÖ!>ä(Aª@²(JX;89d>ÖuŸé[ŽW”aâ>ÑÊJ­&¢€"‚܃ÖÍjN™=“ÕÉZª[…²¼W•(t¯M2Z9‡óèÈ¢UIP™0¢Hvl]#À©éFèÄÇÃÊpÕ„™)ŒXé{tª	øæb,~Xµ
UF‡U¤cez(uЩðÌGÕ€¨îiõÊÌBüׂ¥Âttˆ@qè0*µÈAA*
ÂÁ­ pì„lÍ“ÕƒX—/æˆLˆŠB¼TH­þª¾R.>â:[£QãÕݍ¼ãñé2¼1Ædlá‚j+A@Ó64›®4o|{ãbüoGùÀ=a¿»><	…+АL£F”ëÓÜþiwø7>1è™>îr˜Ä‹d•˜œ„ŒÚy!JiçþʃÿþñìðÝðDM¶8
¬¥nrWEÆ ªß×ÕnÔu›¸Ùʼ– &ÒÿëG(Zº0·ÍÇæÂç†-J8V§4ÏHÓ·
\ÕÔ¼Âúù½Ð“ên'°(×
$¢1ý:žG7W!¥²ÚœÚêì3΄WÎt祰HGYãNCÚ™SäÿcТÖ˜²—vyãµW½ëÅ0C„äzv.VSË#;=N›UØoqLÚkñu«â”o˜ðÔ40ijêãtœee<±•HOFaRçè×Wƒ`‹Cl2åÇÞ«…3KF$¨S7ì>5¥ÍÏ7òóåãË«mⵚ¸’ÙbÏ¿À¿Ë‰3E"Ó•î<=-’_«tn6ÊB£Eë«6çç7#Lž_\39G½Î°©\†ùÃdO#xÏß ahQLã¨YˆÍÃ÷*تýÎÊäPºTí)Ôå.IØ&Ý,i!gS!û:³–à´J8›–"xºâ°ãåÕP`»'¦àG·ïŠ±òE82j,µÔþ?Ç0ˆ–«êšŒ3j_íˆËWëý´Z
Š{}À›ÔðIptE³©ë@šS?ê6n‚ýd®ä½óæü‰¿ãN("ŽÖjU¼V]$¢Û¥hŠ;«§ÝñÂ]…·í½Ã2Ó§B‰TT`-ä:Â(FÙoOŒ¶©ì6M`Ê6‡@ß)ˆuCDбM·)&µ7¢K“Ô´¤üF&ÔÆ=«‰ãN`š0R&‰¥JÒ4Ýõ_ûEÿ BÚç¹\™¬¦]º+Õ;FÚš^8ãA#¼.B¶dõöÂóæs:®…~5=C䈰¦hìG¡|Uað»8镉ç[¦!Örj¶ÛM at r8­éœŒll¿;Ìžc>ÄS&±„kš¬êðS%bun»‘…Í)ìæ6"©$ãoÚÑÊnâ´E†òØæ‰Æn?Š–2ÆnèóãÁ-%.1+‹ÍÄ·"‡‚͉Y'çÏCGc![Wƒ·MǪµLP#£è–ÆpyäÑŽƒW%lLƾÅÜݪl¢¦Qº<ª=åÕ%þíÈöv·$ñé8³0%1'Äüôäb‘%"†¾$NÀKuÓ Žû.Ù‘ÎǍ l,Ó3Ì×ådˆxº{cZã$ÞŽ y$47};;C€3Ú.{Âΐ,¿¬‡¤ùâF1
X8L°,îùê
!ý•æ§YAp•¥`í©$ƒ~¶TëL#΀ü	9þÁÞØH`üg/[r¾&ëŠ
Å}yWv×âak‘XSܐ}Ãït
Dtå0è=nÀTùÁi$º“/D€#¤YŽÒ9p¦VÄ–¦@ƒáÝ[°ÜswóÄÃúɲ¸mÉÌ<ÿñd¢LìT€náO؉æL›3yø˜RïŽhÌCcwq„Nâ
æ„Ä4+QM/|ák_熵è”lˆ–Ó
<•ì¶ñ]„1yH|”—$jÌ ·
zB¾Ã`²-ü§„Ñ"½˜É/Àf€µèêG	õ’@_WŠýAì°îÛ°8´H®:É€[L!ÎVZ
Öü4Û¿=÷%“àÔ#+„¤<á
%ü\¥ß÷:1-)êÌÝÛî!k9ÛúÖ!¡]ëô¦ÿrF¹6Ë|͇˕ܫ’ÿã¿.R
àmô]ß4eR4+áÕÛO8|v½J¤Ž«ÕýpµWNÐhà]9^?±N6í»O`ÌåÁÚAL»¨ú‚T:hÞC`t龎•Æ`?{Áˆ8ÞÈÈRÖL"°b´DƒC‘*ÓkÝ–õÂhLÀ‹Õõ¦krÊÕ{¬§åÁ1/“‚XˆU„C\#¸…º ãÙ®‡R›pŽzùw¯o){¼ÛÜCO7»£²æaë‹ì$Œ§n©±’U
¿pp蟗ZÍ–+Ãôž/æ HÅ5¥íˆ1FÅŽSŠ1f)KdÈï{"„jñˆ1D€Á!‰¿jV¬uýþbÊÕv‡µUŒìÏQÊÂUn¡ª·×r¥j€«IWjõRÚ†
AÑîÔï£õ%æM=ùœÌæ}“|Ì7„ßæÙ:Y“ä‘úCy%(¬em,8§âðŠ¾#¨«"ÊŠâ&˜¬" ˆ~'˜cÅŠ2;x«bÊ‚®ø, ð‚êØ2`
@cÔ5 ¹€†¨%_Iy…ê¦^\92ãó­ADù¹–éË“6³wÍÒ·‡=înoììs9þDŠíÇÌn:µq±ªm¾Ubƒ+‘ZènçÖ½o«ãi7-T|µPˆ`1å¼#ãJm¥þƒMŠ¸'ËåŠgáœÃ»)võrä`Q·C–ÌmÐÞ0»²atbq[%FVºˆÅ
{¨TóBª®]õL£êœu:jõ'zŽØïmFÞŠÚ¹µ—rôÑÓEʦ5ïö¹œ»s9fÕz)ÐÔ¡—¥B¶#Jï(•š`
":Á•9D‘ê‰q$íT|Š‘™‰›ŒvnçOB`Íü(Ê*ÖÓA0ÀðäI.1儵1ãõ1y ²Å2Ú•¶8»Ò?mvسQé‡è<oˆ—ž×jÂnzávóYxòÒϵÓÓçM 3ì†ÞF<—«ô«[H4­Á7üYï¨:ñc8T|óÍ2€óã… -þâ4,1?ÆT(j,LÜźdïÚ¥®[™pq¸…ÅZmé€1ì>eúÿ37§y¯”0ðˆ9Y«
ü"Çor=Ì'½_üEƒD…ê®Y„q6b($ˆ'šÀè‘Æ…à«P#‚bðEáÇéÒ
:¤Ã¸˜ÈœÒ}b­<Z
Aš™±l0ëÆ#Z<‘½ËNVMÁaä–ŠWSÑTȹ1Lf”ÓáT2ÅCcIˆ0éz${Ñ›Æ,P(ËTX”wUþ\¥
SAqÄl8ÇjÆ¢‰À:JÐ.PRbN“—h	àà×nÔÌí(D[€k¤ÄM{‰Ò˜’BT¦åŽôNs®a7ý¹ÿ%ªF&#Qé<6Ìr÷oEåuOÏo¢É“kŽðªjuä¶÷ÒP8½’n¿Ò>Ž^ï?][œ#ߢ$V.\4f<Á¬ÅƒO•ËO÷èÙ€éÊB=£µî×.<ãcLq#iÜÀ¡\òkŠ¤ÿÛ‰º3MúìüXK£ܝˆbW€-§&‚¡d²nÂÖƒ(Ü(=dÙ¤¹_ŠöJ/òµ¨jr5Úcö<
ïäÎ’mÆÜ
Ð?¶	M*½~'M„ªG 0C‚+VŠÓù
ÁÈÉ Où‰sç—–Ý·ü­e±À`öN|ð Ë•¢|¶¬é›@æRÒÉ9å‘ÍËŽ“óE\´ŒˆH‚†ûˆH=3¢ˆ»Ø9À«6åB’Aÿ;h5c˜Ó9©Êl¬–—”–VƒH²ÇCúv8toÁ9!]:ý‘aZÒ‰WÖ¨¶‚ÒZz3H¢è8jQ¿JLB‘m†Ø€Ç…€â1ÀµTÀÔsK§!£íQ‚ÉJ¹”°’H²gK3Ùa +“¬„´TðóÍæÍ„ èY‡¨yIFöH MÃZ]`:ÝY‡œõ!p¨Ü	]t+æUqøPSΩ%Ƅߎ©'‘_VîZv§WHñ¾rHy¯¦0îG…푧ÖÊK:©9iø{j
¦3èWúMOËéŽP#]9ê0!ÐûéE%‚PwZ|_†E,odC£TÌj¢;I$uK¦	,{ð(•úzA.„%@÷¡+‘³ƒÈbjw”¤_NLûGVð¢K§ภ͜€ü³Ì¶ˬ‰U½:°Œé»¦ëç4-¦ƒDÝ	q`2°Eï»_0%víz®[ï5D m1]«øMˆð3IäKdN'JrÞ™g‰šC³€twè#C›–¦~P/±qñ«Õœ!Gµ£áŪŽH/,ÕØÿ‹;‘à¬à4"ð;›{-mbòyžÑfð#öõ»CÆ«ÁqK6ìÿK4¢Àƒmé¨Ì÷ÀéØï¿á"Ùx5º¢­[Tú²Û}Šê¸FãL¥J'Qœ·ŸBœ«ßw\fŠ«<lŒÆ2\O]l챕6åa¸VÑÙ<0ŽÆ€›uaF] Ì¡¾@LU9¸Ôksñ¹òԍa"è854êвҐËÍ& ñ[KànžºÖ.±‚f§K9Ãr%Ò(Ìk9¶KrÍRø¸‰¡`Q.ØпUÀüäPäØ^4^Úàb$‡±¬Œ>PS½Ã1–§ÿáîÅ!%F¬xªï)‹X~ÁXÔ¹s—šyÌ´
õÉÃ;d»øËddgCí4=´¿	î&tb7ãé·’FÉ‚gÖàJ0ڀȺáM(/B¶¬nå
…˜§ÈEŠÄw²p5Y AÇ„ì(ËÂÝ@qýÛ˜ÃPˆ§·ÂÖ<Ü`ôýZiEko"è.X‡Ð¤D9º:óöpåt‰ûQ(ç1o{‰ƒÃÛ‚®y{†Û8N€ÜwƒÁÔ<¹‡¦©á;¿£!þK1ô„DgJPI/Ñr> G Ì0~Ÿ’›™à	µÔ̤”ÒüJm«‚Cž0xì=,ë°—‹é^y²_÷ètµ`Ûà¡Rk3 ‚ºÓó»áþE7^«$@/n(¦.÷ü‚©Q0JÉý·KÈéµµÄPŠ¾ø®Ú×;>}a‡sÌÉhŒæi pqÇïÏ
HYùCÔ¡L±$·F'à1­HÛ9vL0òµ¹
¥SGŠ¨ôLr	EFÂ?ÔÄ%lÇ)Ö>¦¢Š :&†&.<0\ïþdqÞèG”pïqðVâ…ãýIÅԏÒ1…žm¢%„©]O
4Czlh°x‘Å~^QŠ»ÄÞݐzô" ð~©é;&n˜j=L3*ëXsR½Ã¯ÒÌòÐ]tî ´:ÇÏs˜~ ™±mZ(T/Ùª€&ëæ2ñ—©¹‰ œ¦Tœ ¯Þx)Æ-3×fGA¢W ä¯ãŠGé
Cá]Ìš„À§Ú*Ç­ËvuªDðÕzñ¨zÎ:ìª5œm„`ùàNRÚ½W¹›ž²ÚùÀðŠÑ0uX–zû3ej²b at 9kEÈèSë¦i ¥†fui+ɶ¥Ýa—©5
Kuµ”;3F«áq3—çp7š¤k†ÖE–l	›ìzçE¥.¬fÆ‹¥h%Ÿˆà=z‘  ”5–/ƒé0äøÉU;Ô
¿*J&Þ‡XÎôc¾¾…¡
{…5Ér„~û!S €8ÜsB12bŠ,2dœ4Ý& ›-T/î…Á‘ÿܸ&ÉIV§W፰u1³ó›+€)•¬iÝR8ü0cÒvÒÕ3få÷V)™v%âéé^þpjILuÎábíaΙºgS÷Ã
mWpÉŸžD³Öò1DPƯ=]ˆŽ–.yRg)3Ç&z?Átæ'œ]LæÜq6®¾&48¦jÆl!­³ù:,Àd”òæPÔH	Æ•sb+ºddÒ™^й°|jí¡]Ä,Ü6ñESh¹ºÊјª— ÝÀÞ;{sfÓK$ ¥®] ¸ƒ’.#ÌeMà‰hf2Ç|$K	y-ßVê"3d±xãbVô‰T%²ÎOÔ÷¡ÔµÒ‘ñ¸‘¹ìšToå΃U±†RðæŒorõMº*"‚ÄVÝ—žO¤\yOúi#h¤k‚+ñBÚeè,KªfÓµ›s <Áƒ˜ùÛ¡Rñwóè
ê•BSm˜‰K8`ò‰*äÏšÒc@%äþjLTPö߃tµ‰®ÑµË„Š½ÀTøeÑÚíãôHU¢$ëQ°ršŠn†X!Lì…Ö ò³Ä;¥š|²˜…è
~8Ÿ³„ƒSÈS¥SÂ~ŒgS=í7ÑpˆûŽÄÌÙÝ`…‚Ê•Ç×€D–¥õ°Èc
)vƒ^<Gƒ¦¦
ëLšmpú#¤%ºo\û`¦ÃR[W<|€çàLJtƒÐ“w% “1C-ýÃ&ky	º{`|ê¢%`ŠF2VÛ§`ò›"¼šÞ±•‡õ/€[KõG$Êhc1r4°ìŠ
Ž"µ‘óB‘’UDyÄ2Ÿ^²ŒâòÒ€|[­(Y	%óñ(“R£g0M*ïØp`	ö®[xašD£#­üí`Oýˆ'KÅÒ cÑ5Jf¢Â,jtJk/„îÐ\±F’>g*þ'Áø(jXNB}øÖä7õ[:v8Æ|±ëô‡=:Ê_wòéà,Ù
›
ó9Gw îѪ
ç~<‰ò©ZócÂm¿)·›‰Ø·äm^WémÔ5€Ü¿âÆtDõÔ!MÀÄÕpï•p¦²™åœaR¢`P‰³§æ$½z™-‰ÉzÒ–ÚL%(ã6ÔiÎY¢ÁͪáèøÊŠ`4™V“z
4î|EÄ%ÉÆ舥£Ih4bV.#«mlÆXHa5Vq³^×ÙH b‚$ V˜7Énã¤ì¢Lz:+Ù%¾Ù&…ܽIÈ\ý¿·‡-Ò£xôƒf_ß{gÜÉw:D†–Ǧ`øîÂVxBVq?­íbï¤Å½¸!1¸·ÖeŸ©™—gD«›a– sS“F9.™wêùñÕªÃÌë!gjIì]ÆÈ/´E1öM²9&F5Þe\MF:hDæªÐüÚ­4Èù%3ŠéoŒÁc'¬EX(tÍ©³Ä¼äÚVöx•ã-2õq§;›æR̤aãXÍPhjè¤v˜ÒÚÀ¶”ÒQ°/
¥Ët¹f׸—HIQ`
àª
,Oª}ãRÍ{çÏ*Ö©­êm–”rPFþðç'#€(¦$~ê«ÌnÅ-¹c(û=#`ñ¯G07ûæ‰ÝžçL×æÑüî…O½iôw(‘«»¯-À–‡³4F`]¨qFþ#Jâ‡E( {·›RÑ`Y]´aÅê†ÆŠpþzIP,r/`[%’s™ÿje¸+Çp1DøŽ™ÓñN­Õ<#`KöàÜÝ–«'éq-á—¥.[›Ï¶ ÷„R¥‘F£ $RFçœ@H¸ºÅ°a`LæÀ Lìl@ ™ÛØÔç BE+u=-n"e½
	pÜ°v´X#"œBƒÈ=
F7À‡'Ôüېզ)…–ÕîCL•ên#2Õþ™†|‚Ãå˜N˜<ÆOŒ*Dà6‡Û|¿	C)©ÃµÌʁÊu`e 0ýŠËEy
ÇH¾y32¾³ÒÇ‹I~©ž–:VúúÿíäøqÊÌüì­	­¢&pË7˜.œJ›°e{ðqíµ&s—tÒ°øz¼l”½Ýr“ƒ‚Ù›”š°
4ü<(2ñ€ÑÌYl‰
¡ÛÁ‰¿œ#Ú!J‘r®ú9X‹²z|8Uº^ž#ãÏ %Aÿ}Ò}Ú3ô	-Üø0–ÐÈ
7¤/ûËðº‚ò
	|T<5Nû²'©¦Ÿ»÷ô“V,"›dæj"ü‚—ŽHž”C¸B‹…ÙGê°¬+(J™+¬Nüè‰awZÂæy@àVÜ9ٵзÔe§ˆîÀÎ/æF­{{ó¸D,ýž·<!É!Ì´G݃!”&r3‘Éú(ýTD#¯¸ãÆ)UánšòJû%œ6oä«ÃkTüDZo¥J¨Ph9Ùžž3*ŸvÖ>
ÃÃl]Uâa½K¯ü‘ý͘öÉCß=ã4ç´óh%·-|Oƒ]À÷L,gÇ›ƒß€&=5-
hœÔ~øI5²È˜Ä’¾<W9Ù0z;'{ >dæ^ <Ø´š`VÎ
_âZy£‘±qÕÌÍ6ño„L;Öõ:O·•9SÌ׏•6žmt¢¿ÈËL΀¡Sƒuì’e€½ÐL°»C	@‹–±£¹Ö6ÝIïÂH²›T×]Á–Q,ÌcØ”!÷n(gyB9%†Á÷Qâ}ƒX`Q‘ttÉÍ“ùÃÉw9PDÇ’n:º·Ó{ºröxý1P€Fº”mØ1å“ýü‰Ž³“ÞVÂF5¥v5+o»œÊB=¼ÙàØ(•ƒ2ðŽÃšƒìÈÊÄI!owÐ݃*¶å^!‘Â7ž²^˜j\3ÂÂÒ¸ÈA² î©Ê€¦|eý«BÛ¶©Ê…³B…Ao¼?D¡ª‰£%ÕT9Š(ª·k!‹3kÒ
ªª0@›•FxªÍ*T#K´6j§ˆÓÉKC®dˆÔM²Ç~ë˜À¡¡Å¼ y eÿÝÝÜcÜG
=KI¹ÇjΣô.^ž{¨Û8d5S$Hä CQ²"–”Õ¬ëoè•Çy‚Tû^ós–á-–aˆï÷Hf&wxŸãqôE2­¥m$>ûVé˜I¥¤§A˜gŠtºpja6q®CÜuìÒƒ/oú•ÏFÁ@·nZó8u¼5ÝPH‡7\
–Eœ.¹î
hC†4Aœ
ìŒÅ¬äšD|8ª!íóÔmѦ—žÑø†³…ʈûñy«ò1Þâtu7"%Á4 ¾Ð¬)~ºÚ€–:ØPK.¢F²&r
Üò~Ž„AúAI¢#•]Å‹®Ly>ë®
›:ý„è'B+/IéìG!fÍ_þäcäcr<Œµ;ÖÀ3Ñ1†U¦qu]Ã!©CS*°ˆnzTz6]+%£;ôØQ¿_2«&c¯~à3šgÂÛõ;Á,òt«Z©ÌêDFòw
åŒT>
è
ïz‰UíQpˆ1>“	ˆ2 ½:@¶Û»c.R
—°;·+Ó„Æ®~ãtÏ,0Óß
ZìÃE…æ°JXGûB§S·Ñ鞇B·g*hwö4ËTU2Wña鈂¡ʶtÙ?:‘BPEUüX(ãP
?`ÀÈu’ûá«¢ÐXÖ¦2º¶Û,£$$ FmÀ	k ™xú=LàœgÜø‰˜S¥H- at s05\¤µw¼RJÄ;Ú
ûš´,ö Ï,”&¼¥ÙPËðM){%†¡Úà0˜ë*œÍ„×|;gÊEAŠêFb_¯ÉÓ´SÐœP™7ßFˆ‚ZpWÔ¢S.!ÖEQ‚ý°é°k(t„Ôênèð(¡tà&sO´‹Ä„`æAD“Nh‹›nRx‘rph\—x1±mÿ­^.yK¶æÄW'Ì[§Î…^Ö°ìýˆæC÷þ!¢ƒÎ2.5FÑ
Šæù¡1%û
ñZÿ먖A‡¬ã¥’/€V‘Dˆò#eáÀh)@œe!¦p?dhª0†žHëDh¥{Ž¢=dŒ=T[iWOX²²Üé°‚x(PûMµÄ/^™¼Z[ Yo<…³d§JÈQèWfÒjžBA=_=3$¾×›j‘§µG§tª*1ªŸzŸ´òlbµ÷läf,T7Ée¼’e£Z_î†A%4Ïe2“ÌBQt’»òTD1×ñ9:æuæ
‘Äax $]b‚Ÿf`\òáyHd.`ŒG=Ã(?OR€VµéMB(4¬÷e‚cã‘t:›#dÍE‹…ú4²Y$ÒT~·üvß­sY„Š®ðaÁvH‘•„s„ ÿ*pMʵªð(A¦Âª1[·À/øº?*ðú2Ïý™á~©"‡ïÝ&vhµaƼùOÊÕo¤\°¬@ñþm¨k¥¯ #'¡˜¥€ØcX:ö>LV©È²êPÅ/å`œ}<?
”…ßJéçcå'†×Åñl¥	¥…ÐçW„Œ]ÖZŠ¾½!@¦„””,,gBÄÑzAúùÄ+â!mõ©6W¦˜Púi…c|†‚-?MÔEÉòÏwoƒ{÷á£,°ï=×ÑRY5l•%FFq,Š¥}Ã¾²Lëä)ºÚV˜—ÙäÂT¢ì¾äŒÔñÆmD%Œß°½=C’“ˆcÌ
?‚þ€®™”õ´ùù÷G梥M&}¾D:’fœQAg¸uÓƒÐ1ð?
Á»‹ý	U‰­›ØŸÁí˜qg(‹Ò
@>Ãè^„
§Rn:šädt1©Ø©HG,í¸LX˜’ÊžX¼ËUô-IÍcÂy¿]R£¸•¼¬î6Êâ('øÙ¡˜.g1´Õpí@§ÚñÅÇ™yZb8æ™>a"Jƒ“?ª“q¢ÿ~"«}n…ÃFåD#ÉZ«P…ØçARQ[2ˆI¥\µý@Ïo‹IåJ’¾mΘ$ðèWq+<°‡%ôßH~'éí"6i\bÙ÷¾ülñJw@Kœá?»È_´–½œLÿø¬Ó˜9‰ö‘rœ%0–þñLªù;Wì»p•$YR;p{ŒÖÁ·#¢5Æ\’…“_k ˜êuË£VÎn–ÏÊ/CÌTý*=E5p¯à´5:Õ@HÈq Ê“wÆg©„š½$ÎÀ(;Ô›îW䌝=¤óMF“Nm#_Zj"NnøYÏâ”h‡ŒßE5xuù>iAvκ¥æĶpkúƒ"â7¢aüIñŠ)_íl±
ºa¸˜7Â9_<´~ÈEp9QÄw|D"îò÷åTlTô;X9Kø=ñ5…¤2-‚SQ¹˜Dˆ’™€Šn¬Í¨'Cò
…ŒÅ˜Ú€ò,©MLD^ãÓDevijB׎~/y#·Õœ§‚âˆÜ¼33?ÒÃ:„'ü}ˆ:{émF(¡LØ]nIU˜@Ò ƒ®;¹‹L¸²Ë¸·¤whiÝÝ}Ý£Ï{µ>Kº(ùtl.±gÍŸï5D©ÿûvsRòdÌò~ÙÌ×Y2f{…ò{bT+
Xž ñv	Ö˜EFÙ˜p[]Ò†T£Êö¨úõ
Ìe ›C¢+4XˆØ9òËqØMÃøTŽ†2¦8t @{*Y±³á˜At¨ÕȾ+])$X|Õ"'Ê‚Þfæ°#ëÀu‹*—É‚-,	È8#ìÕ‰1‚óùY¾õŸÇv×&h¨6Ü|Žâ‚Sæ]˜J|™n#†Ñ{¤ÙfO9™ÁeÆn [§2P †í1(p
%sÅ®`¼ˆvÓ#ãmªš¨+2a
7'¤2;|q¹É™UÚA+³š€Q-ŠÝIýß Û-ÏFÙN§ñº%>¦É%1[íèûh’ÙHÚ<BñâFŸ&èÅN‡ŒZa3ïÁ<û˜z'¾ªÓK(Á¶¨»¤±Uýk®¯(ô()ñ8ÌÞ9	Ö›Øà•„ºY&5÷uíEdå7Ôt¥fp¢”#ã6³*étZ©-ŠñaT	Z1ž€§ªZŽ¦^r¦®¼NŒOŒ‘‡@é]ˆŸamûB>5Æ1/H§¥½Ê:â_¢ìâdŽ€”ÈP[é‘æ•cDdó[f	G{}ÿ5*fT
™Á­l΂†E„:í»0VOö(–(OSúBQ˜‡-Æ%‡j”ïwĪý–ç†Ìpª–l´Wº|ÇîÂ*d9„HÊm‡5' ¡¤#%)K„°¬*6*[ÃSA|zóZ’W„b0X†%n8(¤J‡+ýX"YŽ0H¸ôŒ‡Bm¥.êh}ñ•Àjä6È˽	{åN&1~µà#Ru[ôü	1.ÑH¯ ,ÈÚ¹ï`Á‚¹H™’}RLš-ôb`Ç&/*S,Œ °á5*'A¹¼¿øÖCˆ§¦¨iƒ	Ä–êÉšÙk×äj¤"ÓÙãjç*¸€1É&épi˜ƒ×Ïm—yÒ„@¥ôêÜXÛ?–!¬¨zJ~À	?	‘òà ­)+õa”ª»LÄIèG£Á™­b gí½Eg!÷%á#.œ7àˆ`|-2 GF6:„#ѨiKF Téᛩ÷S¡Ž ÄÄé 6Cåà/º.]ÎØÕ<—~#0ù¨Âùç™2
ÙzÀ˜Ô"ä|•rÈ4Ñø}Í:˜özN<(:I01pY¥sPz*ñÂÝX"õ¶IÛăÉìIT)üïüEšVbMrlÀv¤÷´û}54Ŭï¤Ñ¹ëïÓ1Á¦j,¦ÍŽ
¾x0¦rz‘\RÙŠAH«DJJ‡©Ê,ØJÞæ9]z¸)毴Ϗ‡Ø,Â0Ó§Ÿ"À&3çYä«…«+"éÕw!ðß32ó>q`Š9BXgw&iˤÍn.b–Š°µ2Éñn¡Lr²]Lò؈&|™³‘h0Í{—¸ÆZmå2
UË{d-Jà÷Ø°<Šb‹üp©¾o(2B¶†Üå–]gÈ$)?;œ¢,kéh{k–@
ÁŒ{nÐ"[?8&Äö–ªs•J§Ø­öê›þ¦4ì”»˜ê|ª<cHpA@'×V€ÞÄAŠ¹œ¶3ð„ËçŸzèÑ4÷ìj~FÖw\W«§>R HܲÉäó@ß,VÓ0¶l½ˆŠ™úk§»z窏DDµ¼6cNS	::õhetÞ¼< )«»/Ì»¦&)·HÔ’R¢3ë’X¡{2¸Övú•È•CÐP1ÂA°‘µBÉœ.âÙ–e©S÷
¢ÅT/²¹™\–„Aq`—?\ÓZ…–Ç=·Ž¼ûdšgfÍÛækHözc< µfåxpÝTa|ÿÔXِÄJvÖÐkùÆϧ˜²“	hëS(dµ+Õ§Áú(^Q„Š»	°'½(^@jÄ4H4R‰Ä…bñt”ñªo(æ™ÌhHG€	RÅT6RcNòÐÇ2t<
±Ù:ݪöVî%·j­
mÚ¥4VM¥yÜ°ñ›Dòö{.Ò´–Ê!}h&fY_·‹aÉú(Ïë!蒍˜E
w
^ñƒs|ÝÆAAŸàù¡ @¢mÜ8ÁÞPŽ°ç|ÒºÃ!%©$#kÃs}„0 ¡1(ª­`NŒ%cc_AœP¨Lϳ×€‘Æ?­ t1‚ЫvxqØéW%
’oÎÝ#	LsMe’õÐÓwÀ¹I*a±¢ˆø¦`Á‰g/÷’p|-®y“3K9€iÅWFÙ7ÙeJMË—m†	y¿Kì?ìFÛ)ÁËŠ|Î,b§Y!ü*˜ûÒr1UO;!¸ö˜ˆíKƒV’8 ¤äz_.Â^„½Eªe¸ÓoB3¬¤M#
,¤‚’	&ÖÃì"Fî%ƒ”·!§¹&ccÂ{ÇÈ“ºôT.h-ÃQwù2ÉO0‚N”Ó—IüyoOä‘è`bk¢†<óBKXXóX%ÙÆïÆm=&
ˆÄy#‹ÉáYiÁån#mÄcÁ³"®Æ—ӘǢÀúè8ü©"Ò²4¥Ý©«·˜¸EMÿóMüeð‡ZóM|Sp1Ø^{·'†ú ´}x˜Òp¬†¼¦É†+5ê³Ú€	Fû(Zq÷®ì­ˆ7g72o2‰ûòðvA°ŽîYW,zÉ·«x²ô	Ž7^ùwðx7âé8S¾ÈÉÁN	Å™ëé!ãèÀ]·Öš¶lÖœ#áéçXyù-÷‘JñãàJë|/Ý‹-±Q…©_#‚@L#ºH_”IYLSfÀmVIDX˜bÝ"}µ0)ß•%xäiË §‘„çlÞ49
Êjûˆ0®œ
€È«,x¼TV²ªÑñ}‰JÀ;ª³t`%ÙÂ:ÞCiabAÚÜúaz@ÍS(&â2‚3š	¢C¨­ÀGˆÈà-hr¢å‡\väEg1e€ü°2°ÈZ!ÙÉ"x¨”gÝaPP¦=Ùäå ÷ØnOO%%ò—£ëtôTÒÀŒ1@ˆé"ð©¨bÅÊ’NÚ½"¶žÀg>´|r_é#}˜JÍkÃ@
Index: modules/damieng/graphical_editor/daxe/lib/fonts/STIXSubset-Italic.ttf
+++ modules/damieng/graphical_editor/daxe/lib/fonts/STIXSubset-Italic.ttf

ô
!
 $'*038=JP\jt{ˆ’•™ž¥ª­ºcfruwŠ’˜»ÆÙÜí„‘£ÐÕØðô!!!!!!!$!<!?!E"ÿÿÿãÿâÿáÿàÿÞÿÙÿÒÿÏÿÎÿËÿªÿ›ÿ˜ÿ—ÿ–ÿ‘ÿÿÿ‹ÿŠÿ‡ÿ…ÿÿ~ÿ}ÿ{ÿyÿuÿtÿrÿnÿgÿeÿ]ÿQÿIÿFÿDÿ=ÿ6ÿ5ÿ3ÿ0ÿ.ÿ(ÿ$ÿ#ÿþpþnþdþcþbþQþKþGþ%þþ
þ	ýúýdýXýWýVýTýSýEýCà8à.à*à)à&à$àààà


>
|
°

	*¥¨&'
>#$!-0=*1#!)9"'Ë	#% 
@5
(&2>!&
%/0;*
&35+$
 è


ˆ
(Ã-•ƒo)2cðý×>"öþù

1š'!ã	ë)Õ2
 +º$
"Å@*;c? Rcn"	#9W
@þãB-õ)v0--
6. (*þ"'éô
 2®. $Ƥ4
{A*÷+|"-*.5™$%áþÛ(×í&*½2(Âme$
,=MC$))´´0¶% *þS&U
:3DU[\Qo‚e|ŽrD`6#XV6H*SlBR7&;ÕE<nT-=D8rT.=©)(<{a]¸F^†guá<%9!,"KjGR}C¹IK[|FÀ
,ãPF4<
"Q?:I"'-gK4!"
›ÈSP5/&3CDR2Un à	I^G9!5%*0=)LX

þEhT

)ë),¬,ì'(¼C	(ý¼Åþ;F-
þl\A&þdm,)
:2NLC}-=.#	
0`‰6<&
r;I;=8wØ£1þÎ.5“>"'b˜
3*!<˜p12#*[mlK>uÆ$ BhR2»,/01'a
*:klH@ˆ"-	>#X[¬~/M#
mw þÀþå#¢¤
/;Ln!?e"]CT75'7+0$PJa9' dH#(;8þ‡#/&!f7

W!þA17R± þ©"

#.#(0K0KRx+%¬7aX*?.#/:S ´2&	þ8!n
G68
	
+ik.3ã
	u4is]ë$O@'Xw·-ÑÓ &rTU#û&5
8*uF+;¢6EI1Ny^
Úvf!#Ë25
ºŒt&@O9=I
	Ÿˆ(%-FO42:

@(A'	g
79	F 7#%:aUD		
ŠŒ9$	X[
Y 1OÀªþ {i  [þþ+#6˜&=^[¤N
x¡0

+2	X'"R
P%c9Q> y †)(		C/&ww*	1t|,$
>!
)B=ºnns  Wځž!Cû3%
º
+2
-,*	t%t
#C20&!#%#2V


«î 

j
 %F9

&,C4&7)5	<B%&0)`
(Ã-•E Œƒo)2cðý×>"öþùàj
(Ã-•u¢
«ƒo)2cðý×>"öþùâ 

j
(Ã-•u$R‰'1ƒo)2cðý×>"öþùàhh©
(Ã-•qI'G$.' 6)ƒo)2cðý×>"öþùdh/:0
(Ã-•{ȃo)2cðý×>"öþù!
(Ã-•J;)+9;(*''%ƒo)2cðý×>"öþù|R::*(;€8&'&
&,C4&7)5	8Qaq¼i1D˜È£5-rºÍ4BOF4%&0)Z‰^mÄv
«š-	é Aèp"'N¢+º%[ 

j
«.þS5)¹.[ 

j
«¢kÄ>G|n[ÁEa…4GQuH¶NRW_JÖZ 

j
«	$*,þãt54\H7¡½ "4â&5Al|éU[ 

j
«!þò{g/Í("n.°%[ 

j
<*-©?„øNZr
«oD*)ZTI=7Lžf:0þ¼') \1x.Jbo÷ 

j
*m^9;W1&.Y„;6º#! "DIÎ5<$"*&6I·>@& :+!'"7/M3=B8\F3/O¨k/-_H#@@,!2V4B at 6<):§
&,C4&7)5`Zxu¹.#&0+~@6<%,
:17%&0)
3*!<˜p12#*[ì ŒmlK>uÆ$ BhR2»,/01'a«j
3*!<˜p12#*[¢
«mlK>uÆ$ BhR2»,/01'a­ 

j
3*!<˜p12#*[ $R‰'1mlK>uÆ$ BhR2»,/01'a«hh©
3*!<˜p12#*[)ÈmlK>uÆ$ BhR2»,/01'aì
$r Œs?39Z¯"þª"	#¥j
$]¢
«s?39Z¯"þª"	#§ 

j
$£$R‰'1s?39Z¯"þª"	#¥hh©
$­Ès?39Z¯"þª"	#æ
8*I'G$.' 6)uF+;¢6EI1Ny^
Úvf!#Ë25h/:0
«-9{1SIBSž*K(-1[qs,/S.â 

j
4+1>*w	#+<‚0G~ƒ'=sÄr”þxR1‚62þ¦….5™G(
«w	I.0-˜’e5n‹:	þ	“2SnþÕH ¨ 

j
>!
)B=4¢
«ºnns  Wځž!Cû3%
ºï 

j
>!
)B=ÔȺnns  Wځž!Cû3%
º.
(Ã-•õ33ýƒo)2cðý×>"öþù
«	%ƒmZ57¯>g@CMufUfuE0 

j*È£a8 UÍ4COF‹zg»<I
«k.W8GLD7MZ.;(%?.[.+'@î 

jþ}
:2NLC}-=.#	
0`‰6<&
:2NLC}-=.#	
0`‰6<&
r;I;=8wØ£1þÎ.5“>"'b˜
T:A(MF+S429Ác%¶»
*ML/,2B-\;0'×ï.
r;I;=8wØŠ'(uÇ.5“>"'b˜
3*!<˜p12#*[33þOlK>uÆ$ BhR2»,/01'a
3*!<˜p12#*[-þ-lK>uÆ$ BhR2»,/01'a
"Å@*;c? Rcn"	#9W
"Å@*;c? Rcn"	#9W
/;L-Ò!?e"]CT75'7+0$PJa9' dH#(;8þ‡#/&!f7
"Å@*;c? Rcn"	#9Wý¶2&)W
@þãB-õ)v0--
6. (Âhh©Þ*þ"'éô
 2®. $Ƥ4
V!!·þfyr"%Nh$Aþ¹A17R± þ©"

#<"‚&		(+!ñ.
Pñ.>'J#z	 +/R0¢'1		(
,=MC$))´2&)Vþ·´0¶% *þS&U
G68
	

,=MC$))Q  !"´´0¶% *þS&U

G68
	
3Ž6.4PI#**´´,¿;0;È%!#µU1SÃ;W
(xJ;{`9y~b?* $1$	":PƒPÕ»þ¾%),¾&
M ZA"-')HE2! #K`&'`·Úvf!#þÜ{q#%Oh%DEI2Ow^
«Í 
©Os\imcwbOp}kviE<cW5=C?\N;F 

h 

hþàkÄ>G|n[ÁEa…4GQuH¶NRW_JÖ
«Í 
©91SiFMeO8@>OT&!C=J*$=<"(î 

h 

hÁ9{1SIBSž*K(-1[qs,/S.
*!:."$$A/5í (!0<$1(7ijD&R at F\+%/!$ =0%"$%/>' C?0$#4J+0<+¹
"Q?:I"'-gK4!"
Ähh©ÒÈSP5/&3CDR2Un à	I^G9!5%*0=)LX

ìhh©ÛŒt&@O9=I
	Ÿˆ(%-FO42:


)ë),¬,ì'(¼CÄhh©à	(ý¼Åþ;F-
þl\A&þdm,)
Y 1OÀìhh©ëþ {i  [þþ+#6˜&=^[¤N
x¡0

>!
)B=ìhh©þ%nns  Wځž!Cû3%
º
+2
-,*	t%t
(%?.[.+'@k
:2NLC}-=	5? S+	
0`‰6<&
~€[7M *D!gµ"!'k	4vþÎ,a!®žcn
#.#(0Ñ[<J6þÔ!n7aX*?.#/:S ´
G68Ý
	
-& in?KþMþ³UC6wC!|U
™Q•F1!$K`&'`·Úvf!#ý×<DI1Ny^

@(A'	g
@
ø	+?8`2jMHZ#"h. É&†à=.@:8A`KWaH)B(

 =!'(=	K ºnns  2^VoI+#=C!-4ô0%	ºþ­-b%'
).>&7U($Ö]x<I6þÜÓ%+–h3A1KÏP<0[o
q&'`CJl1"
8*uF+9¢<LKB•X¨<*
Úvf!#Ë25
SG1!$K`&'`CJl1"@þ…1<<J<;!!<DI1Ny^
Úvf!


0c®A
7T

«Í 
©î 

h 

h
û‹™
%Q
(Ã-•ƒo)2cðý×>"öþù

1š'!þ'2
 +º$
@þãB-õ)v0--
6. (*þ"'éô
 2®. $Ƥ4
{A*÷+|"-*.5™$%áþÛ(×í&*½2(Âme$
(®þf)2cðý×>"
 2®.
$þß3+8
1<$3 %:\Qo4Œ" /ÅB>iXÏe•
›

FY\4;%5þß1/;Å"*/5:1;
'MC0>6)A.›)A]3Ò"(Ö’f686R/3Ù%	7ñ='+48
$þß3+8
1<$3 %:'ÞQo4Œ" /ÅB>iXÏe•
%QBþû'<J~ikTD=w+"+G0jœ”¸o0C¿
%Qþt
co-H%<W/$
	<>		83*'
WI!K(’ KR	
	10(î„
%QþöÖ'þ¿m>2x/#»aj12		
=-)
î„
%Qþ„A17R± þ©"

%Q1ˆ;O/m^?h*h]
<	j-µ'#Nw56(
0$%>$²¬þ{"!G'/2JbŒ)&&‹=9DS-
co-H%<W/$
	<>		83*'
vÐÏ 7
WI!K(’ KR	
	10(äÖ'þ¿m>2x/#»aj12		
=-)
rA17R± þ©"

8>b" '>$]<LV!5bá«'-A¬,@ Ó55
GƒKKP‚!MO!u	G0-hyf$S%(#M	þÖ	0Á:zþÔL

þ†
vRŠ&&;0I
N’#+V.>f2‡„ž7'sȍ)æ.>fO¦h‰
vXY
<	j-µ'#Nw56(
§3OP,5wmÂÂbM£* o‹>2(ïïÁP$&Q
&@)¹"$D$&ÛÂÂ&W%‚!Tf¢:4¢þ^5†+2C$
A35L
–eQ5&19!J,#*#F/0%·!&SIQFH%"HE*'`„A/‘>^E*)-*:.II"2X*u3* 
-þ2A17R± þ©"

<	j-µ'#Nw56(
%QÁ9{1SIBSž*K(-1[qs,/S.
%QD;O/m^?h*h]
<	j-µ'#Nw56(
A35L
–eQ5&19!J,#*#F/0%î„
%Q7!&SIQFH%"HE*'`„A/‘>^E*)-*:.II"2X*u3* 
1<$3 JE˜- LSp1Œ /ÅB>iXbd
9#.B#q@¾þŽ#09#M
.! 9#,E#¬J %IQFH$$'!HE*'LC3/<pJ/;^J%XT.;X"2Cd]&
66Q;B		
1š'!ã$Ò2
 +º$
&0+.B")'*G¢òþž3%#H*";E¬c.#&"& =K¬‘
\‚}:;ŠˆgGTþè
@i	´	·
¸	µCOÔAHº”
!NN4?Z\08\Z
}E<cW1QþC?\N@¢kÄ>G|n[ÁEa…®@:GQuA†E at RW_O
+(=./SFBHG|D4¹ks; %8@$.
:2VAG‹[
+(=C
:2VAG‹[ks; %8@$
RjN›NþöN›Nýs5þˍþÍ3%ý½Cý½
þ´Lý½
6VAJ7sJ%HS^C¨CI
%+b*3VáZ9?«ýU0;VBH‡].%ýŸqf#&:
þœJ,F…F%
&<¼*@jCEC—jE=F!ƒ%IpC6Wqþü1ht[;\Fm¯0'MhþÁ,*µkbvT7
·
ÕÂ
	ª
al additions and corrections provided by Coen Hoffman, Elsevier (retired)
t
n






 
v
Œ
â
â^„ÖÜêÜÜ








Index: modules/damieng/graphical_editor/daxe/lib/fonts/STIXSubset-Regular.eot
+++ modules/damieng/graphical_editor/daxe/lib/fonts/STIXSubset-Regular.eot
”K

Ò
M


¼
¼
}
.ÿþ
Ò
ê

ô
ð
ù


'?Y\`~„‡‘£ÐÕØðôQ^bjr  " % 0 > @ C N R _  ¬ Ð Ö Û á å î!!!
!!!$!+!<!E!S!!¤!®!°!¹!Ä!Í!à!õ!÷!ý""";"]"`""¢"¬"²"º"¾"Î"Õ"Ú#





H
p
®
â,‚¼äü
 4BV¦àR‚¶"`Ê0ŠÆð0hžâBvÂNr¤°â8ž²Ô".<b€ªæú4h|¢Àæ$^¨
Z–ÒX„²Þ\¸î$X äJŽÒ  d ² ò!N!Œ!ä"*"Š"Ò#6#x#¶#ô$0$t$¶$ô%2%v%Ò&&V&š&Ò''^'¬("(t(ð)J)–)Þ*,*p*²*ò++D+v+–+à,D,|,²--V-”-Ì-ü.0.\.¢.ê/ /P/˜/Ú020‚0Ø1(1|1Ê22 at 2r2Î3$3h3¦44V4¬4ü5P5¨5ì686Œ6Â6ò7*7^77Ò88f8°99P9z9Ä::J:ˆ:Î;
;V;Œ;¼<<H<|<ˆ<š<º<Þ==x=Ž=²=Ö=ú>>:>V>h>z>Œ>ž>°>Â>Ð>Þ>ò??? ?2?F?Z?f?~?”?º?Ö?ü@@6 at H@\@„@˜@¬@¾@à@î@üAA(AJAnAŽA A¬A¾AÞAôBB<B`B„B”B®B¾BÎBâBîCC(CC²CÐCôDDFD\DŠDžDÔEE<EREŽE¶EöFDFfF²FèG"GZG˜GÈHH>HdHŒHÀIIdI¶IîJ,JzJÂKKXKˆKàLLlLšLâM.M~M¬MäNNBNtN¨NÎO.OZOšOÊPP at PjPšPÞQQDQ„QÀR
RDR‚RÐSStS¸SôT>T|TªTîU.UZUpU®UÄV
m at m„m´mðn*nxnÄoojoÆppXp pðq<q‚qÀqòr(rTr~rŠr–r¤r²rÀrÎrâröss:s\s|s¸söt2tlt‚t¤tÔu`vv&vJv|v”v¸vêvüww at wNwhwtw„wüx(x(xpx®xÂxÖxâyy"yPyŒy´yÊyúzzz,zZzlzœz´zÔzò{Z{Ž{Ü|D|Ð}:}^}œ}ê~H~º.l’€€v€¨
:lžÐü(T€Êò‘‘D‘l‘”‘¾‘î’’r’¼““L“œ”
žžHž|žˆž¢ž¶žÚžîŸŸ"ŸBŸ|Ÿê Œ ô¡¤¢Ž¢ú£~¤
­N­n­Ž­Ì®
®(®F®r® ®Ú¯¯F¯x¯”¯°¯â°°8°\°–°Ô±
Ë&ËBË^ËzˬËêÌ.ÌZ̜̰̈ÌÒÍ ÍŠÎ`ΦÎòÏ:ÏZÏtϦÏÌÏòÐÐ.ÐLÐjЌЮÐÔÐúÑÑ8ÑPѐÒ

,	86!+8	! /)2!-&	
S:!".=8331&.V
[ý«!
.X24




S:"".<ª. ! ý¨64	4 1(/R
A××
/yLp sYwƒ¦{<F6B(/:S
ÓN&7EM¦Š $:
ö$8þJ5$

LGñV‡$-G#€GšQ#+H"†


„\$"&!B0II71F/ 16--	0 -+/¿BP ".&'
f9(;"%'!8(;©þÏ&.~^nŒ)þ%	þ“ô+.Vb7//
YHY1)B7/2þB$!!(5

+SÂE
1"3ð0?UÊ\6(:á+	&ú!"
	3-")-?4)2K		ID* .CN:A
ˆD7""'
0IX5&;eeML-Ut þâ0*
.ž"2RVF:û%þ½"-ç'þ©#
x Ä	fc/Â2þ¿2L%ÿ
¥#*D%		*xÄ<
–'O9 &*„(Í pa.ÂþgjW!,JXú		
‡/!Zþó…Tˆˆ•!7vþkB

	2/-$,Q*2%E3/'
'"2W		$

ŒþÙ34?O

þþ³³ýN²þjþ7þ­

þþ³³ýN²¥=&<M ('+'/ 3,0#4


uBJ:1ýÛ
„\$"&!û` ý³0II71F/ 16--	0 -+/¿BP ".&'
„\$"&!û” aþG0II71F/ 16--	0 -+/¿BP ".&'
„\$"&!ûgg§ý 0II71F/ 16--	0 -+/¿BP ".&'
„\$"&!~91.14ýà0II71F/ 16--	0 -+/¿BP ".&'
„\$"&!P((ýÔ0II71F/ 16--	0 -+/¿BP ".&'

„\$"&!ŽR::*):c&'&&ýø0II71F/ 16--	0 -+/¿BP ".&'
3+Aå¦%)+(U[:!!JšT ( &*;0%$?1)
.>VaKB,ÎB88¬4!2-&1$;
yY€E3=*.`NVm)
QA2B;&5A0tÏqUa|giŠ$8O(A@))2þgW@)VS5c
.ž"û` ý£RVF:û%þ½"-ç'þ©#
.ž"û” aþ7RVF:û%þ½"-ç'þ©#
.ž"ûgg§ýRVF:û%þ½"-ç'þ©#
.ž"P((ýÄRVF:û%þ½"-ç'þ©#
–'O9 &*„(Í pa.û” a9þgjW!,JXú		
Y7D#8B{ F§þÕPrcm”š0(¸!þ¡ò-kT».
–'O9 &*„(Í pa.P((¬þgjW!,JXú		
„\$"&!û6þ0II71F/ 16--	0 -+/¿BP ".&'
„\$"&!˜SJa+ýà0II71F/ 16--	0 -+/¿BP ".&'
##& ~\$"&!I)#'#1#;I71F/ 16--	0 -+/¿B% 'ì ".&'
þh#4@Cw`j•+i'xþ5æ(<fU\o 
ÓN&7EM¦Š $:
YHY1)B7/2þB$!!(5
ÓN&7EM¦Š $:
YHY1)B7/2þB$!!(5
S:!".=b0ÈF83W“¦aV
ÓN&7EM¦Š $:ý†8331&.V
í4š1~91.14ýž8ã#7þ¡8

(+ñ0/ñ.-,&. /E,UP0¢%'1'
	(i>#'I${?

	3-")û” aÎ?4)2K		ID* .CN:A
ˆD7""'
	3-")ûgg§þ‹?4)2K		ID* .CN:A
ˆD7""'
.ž"~91.14ýÐRVF:û%þ½"-ç'þ©#
.ž"û6þRVF:û%þ½"-ç'þ©#


.ž"ŒR::*):c&'&&ýêRVF:û%þ½"-ç'þ©#
.ž"û”`”`þ7RVF:û%þ½"-ç'þ©#
–'O9 &*„(Í pa.ûgg§àþgjW!,JXú		

‡/!Zþó…Tû” aþˆ•!7vþkB

‡/!Zþó…T<þ6ˆ•!7vþkB
f»»9(5M6&(;ý…&.~^nŒ)Å%	¿ôQa;D+/
$,$aQKt0
		ú[
/Â6°,!39l6Zþ6
$&72&*\5	,eeM< $5<›-		9%t þâ!

ƒ°Ô ¾
ƒ›]
ƒ°Ô ¾Ô ¾
Z/);
Ç

;*)9;*('&&'ŒR::*):c&'&&
û‹™

(š"û`”`
Z/);
:		PR
=
	5
	?N		N:	<P

O8
3IcJJJcI3LjSDSjLL(<  <(**

S:"".<ª. ! ý¨64	4 1(/R
)þú.	!%Ç*&.Îö#%þüts,^r5nèýÍ4$îþí

j.ƶ‘’µµ$µr0+8CU: %Z1KN3Sì"%2D2!:,$L.š¿Àœ˜¾ÃU‡$/C$…H¶S-0P
ö$8þJ5$


'®!N*;	-Fû„ Qþ†;Pn68ƒfcŠU=ˆþù.YœàOP"LX9
4 6M25(9($1	-+)04
ÿ'Ä–
1"þ‡
'®!N*;	-F;Pn68ƒfcŠU=ˆþù.YœàOP"LX9
*(	(-1`=^§,%"/ý6ZA&*Dþt,fN>V143TG
ÿ&¼É
4 6M25(9($1	-+)04
$ 12(L›1< 8«)%IDC/šPšEI@^.# $	ÒZŠW(#?
ÿ'ÄÌ
1"þ‡
#A3­-!
ŸãU$ÈCW%)6Ê 
–]2)
$ 12(H¡<2*#!«&+4!(''/	N5s‰D_-$ $	­=l$53 ?

%/‰5^G3^ D>RA;!$8w]#KBTFNG1
$ 12(LHSžZHM`SN;CEI@^.# $	Jfx®A
1"þ‡
$ 12(LEVÉšêvj	8"2EI@^.# $	FL•</
1"H4#>7É¿‹*
1"H4#>7É¿
\‚}<<ŒˆgIWþß
¬©t.¤S‡§_c/-†‰bc$"ca
×A69ˆ%IM:%ƒm^ˆZ=E**˜U‚PO?pP%?@

-3IÃÃG<Uqsˆ[b‘‡'r> Oq
-3IÃÃG<UQsˆ[b‘‡'r> Oq
ÿòº–
	'/9 #`i¹nX`Ua#9Øþ¼%?+3 )+.A07%!9Þó;R“
Ó8)B¤8" ;º
2&$FH#%'%#}3.D"$
(
P¦,&#\þþP&	
+$;B(8'<=&@;$+
/#'6#/&/!†<	Ó9:ÓH†!,G34HC##&$'HF,17»:! ;º	&1
HF'$&##CH43G,
"W6FlZì!\M at W
Ó8)B¤8" ;º
2&$FH#%'%#}3.D"$
(
	'/9 !8%AÌþ¼%?+3 )+.A07%!9þK5#
ö$8þJ5$

P¦,&#\þþP&	
%@Œ1%;±þî*
9  9!9É.=]|;  ;sB5
Ü.#AþX9!
0—1	:£ 7;:!9µ5#(Aþ*â6 %@þ)à7$#@þV: 
aüø8!!8
0—1	:£ 7;:™#e!9µ5#(Aþ*â6 %@þ)à7$#@þV: 
J_+2(74>5"67?[rfiˆšm-]kR6]	yÞr at 2UJ€M2c
KZ&Ð))m5<3þÄ*5
|#~	)H
6(3296
"9!$$(7$¤'ž6#~0
@(3376
39&Ð)82
˜5<5þÂ*75<
)@T('N.#Ž
KZ&Ð))mü” aÇ<3þÄ*5
	3-")-?4)2K		ID* .CN:A
ˆD7""'
«

"9!$$(7$ü” aþ¤'ž6#~0
@(3376

!u'(Ð&b*&Â'þÜ,<11<,%&!þÖ2,

("87?çiˆ‡gh€]V82UJ
,A
,AY;2c
u Ä	crÛ{""þë0/I(ÿ
KCj›ÝÌ
ŠÐÐð
,	86!+8	! /)2!-&	
8A=A3<)·Yú
$Õ!CGGAÉ	
M;›…"&"y–
jj)

jj)

F?
jj)
6)
:		PR
=
	5
×	?N		N:	<P

O8
j¢R:/n(#$’O9'(%A(9m4G.*8Mb!ãXeRBC
@A9•<$0<6_nV=“`Õ79E]¨þ"r+0 œ:7:/03OEZ! 0<*Ÿ}>£þs( 
	1;1%L.+9JY·‡2!:M>V)•hJ\.'‚&>8	ˆ@ OND Z„68-%-_T/!“A
CAd2#IK'!5
u8Y",G>2lsÕ"+#<!7.5,4þþÖX"%³%
dª9yG3)?'"<Wp
	P0<#S4.¢,3O:805PAOwöHV)Ln(!;2&W/S7'!B.E#7G
ŽW*N3Nic, g!"\þ+!
	þ¼015$
	(+´3D\\BC^^DŒ‹ˆ©W~¼#A)5 )+.A0þ÷ç#A)5 )+.A0?

þ~ù0þ
B@5­S…€£%%
0	"9G5V@^(,%x"es/#!=70©–H3(3h—‹–10–c)7õk¨G;k¨;¥@&
#,5	X
à¯A¥þ°Nþñ–_eC_?þåN/bò"þÑþñçã>ýÂ
=/VE

E5*
24&5
WF9@$6"J)2N=VgIœ@‹Ü™Wt\g‘,'(R%65 ;?)_s%/!"LM`MYR³gureIs
9F)Œ]?lK4'8X5<#'0G 8bAs¦+.'"1/2<5R#D/E$14!96þ,Oj-Kck6'UYG-%Om:>qd;
~BA€%¾
J°,3qLEÉ’Ë-T†y'07(v	
,BHXnQd…?dÈK*XkNB^7#0'5]mIBDœf[}~okiTKIHœ
v¬’>15=Aj7I>q
8xB1F<&/"(O?R`?4DlYPO{
OC9Qc|Ce
T–I<NA88WYÍJ=N6 at 88!dT-&!%	þæ96 %$`6F6Ef
d·'%P8YLa0 áþ°-#„’,&•ž¾½&6& XÛÄJŠW"c5K)2
-!% f…ò¶þÈY-.1 ‰`}Q?
Dn—G'0""$[25Š\8|6&ßÎ;:.-^1&>D”3}š,%\d"4
	49:8&/">*cDQ&3-$	þåÊ2B#&029-	$
AC¥;e Š$(‹#7%(>)k1gRFHE*::%"%
.{A!*¥	".#? =–,+N0,!’0"-)
.=
^PKO"—^4-2R,#I"Ë€p{m>]I/L[;lz&@aƒ~gý—'?i
Teþ>1Åþ´´ 	k”@ .(FS ,&-›ýN²þjþ7þ­
T@þ>1Åþæ'ûeN)6"B>:D&0Tq(”@ .(FS ,&-›ýN²þ¿UlT6 ,J@7=0%C/Q
T@þ>1Åþ}+,hS(3%&/*4+8'?-.>”@ .(FS ,&-›ýN²¥=&<M ('+'/ 3,0#4
T@þ>1Åþð7F´Ë/7}‡”@ .(FS ,&-›ýN²þÅZZ1
þþ³³
TŸxL?>QcJrŽþÎ-L,4K-YýN²@ .(FS ,&-›
1Ž(8
1¨6ýŠw3|qs
1Ž(8B1/
1Ž(8B1e|qs{6vý‰3
1Ž(81//
1Ž(81/
2(81//
2ì8ý¾4ztt{6B3|qt
%\~88Fm/“M16‚X%



ý0lF88}–X‚61Mým/‹



•%


‹/ýmM16‚
qgýu


%X‚61M“
0,5¤¤
//1¢¢B¤¤5,
0,5¤
1Ž(8£š2C3|qs{6ý¾
1Ž(8£‰¤-5’
1Ž(8¢‡dý½3|qs{6B2
2Œ%:¢¢1//
0,5¤¤-5’
6€7
41&ýª˜7€
41&T
6€7–ý¬&1
//1ýÓo8h{sq|32BýŒ6
r|{r@B(ƒ5
3„=T
5/1ˏŒÈÀs¡¡r2 n€n3ÉÉ
7~.2¢æ¢BÈŒË1n€n 2r¡¡sÉɍ3
36B3#€þf3#€4
36
Z7U)%-¥¥B††"!(
"”L
‚‚,N›
v*	ƒƒ#M›c"!(!"(
f0X)1 ¥¥B†B¥¥	-%)U7Z^(!"
Z7U)%-B"!(
Z7U(*!þ
†;:ýyö *)h?‹yIf
f0X)1 B(!"B	-%)U7Z
f0X)1 B(!"B!ç)fIy‹?h)* öýy:;†þ
!
"”L
,N›
v*	#M›c"!(!"
f0X)1  1)X0f
Z7U)1!!¢"!((!£)fIy‹?h)* B *)h?‹yIf)*!þ¾!­b;:ýœ:
1Ž(8
1BBBBBB¨6op3|qsþ›kþèkþèk
//1B8(ŽWBBBBB
//1B8+kþèkþèkÖ{sq|3po6
)X0f
Z7U)
›Ø¦t~,€u
›þö›
)X0f
Z7U:~,€u¦(þP°)h?‹yIf¥:¥þP°
f0X)
›
›
)U7Zã€,~t¦
)U7Z
f0X)
›
Bt¦u€,æ)fIy‹?h)°þP2°þP¥:
1ÅŽ1
/*6B8¨6ý”m3|qsþC{sq|6mý”6
0,5¤¤
//1¢¢B¤¤5,
2Œ%:¢¢1//
0,5¤¤-5’
0,5¤¤¤¤
3‰F’p
ý´[NÜž:h
?€žx­'.6
ýæ
3‰”[N
?€žBž:h5­'.6
þ¢
3‰F’p
^
3‰”[Nþ/[N
?€žž:h
?€žž:h5­'.6š­'.6

J`Þo–ýj–ÇÇþýþÔ
>.ã.?
9&W[#"þ""Z
&<¼*@jCEC—jE=F!ƒ%IpC6Wqþü1ht[;\Fm¯0'MhþÁ,*µkbvT7
ªkIB[5
B’þ‡[5Ò´k

rt

X *M
Q%;'pp'T!!U'ts'Q


#C5±*¹qr]
°þP5
þíA××ýË5þP°

Mn;1E
Mn;1Ü‚Ú"WZžpE]„‘#?A'"QT¨oE]„þdnM7\nM7\
1þR/UF-"QTÅnM]„
Mn(F! ;E	Mn;0Ü‚#CH,-"WZ!I	"TL]„’/UF-"QT©pD]„þdnM
8nM7[

Mn
	#E	Mn;0݁#CH,-"WZ%,,]„’/UF-"QT©pD]„þdnM%".L*nM7[






++06=[HŠK/6üU€miFNU at VÖ”U

ÀBþöB

A××ÙB


A××ëBþ÷B

A××þMLLB†BLLB†
AÖØ?
AÖØ
þí?ØÖAþï
¥ðÏMAa‹+T6
ªäÄIAZÔ€?‡ÝÈ\@
Øl-o
A××wJ‚@@N<Q{@@
1<!YF>ùK26*¬Â:üþô=S@XÒ°…2J‚B±ÇN<Q{ßy
¡µ—TAaÐ}?Û9"/WC
“ÌgØ7
óA·ºèó?¹¸Aô
ôA¸¹
“|QCT·cBj•uæMAþ£?‹
h¯wByrƒC‹¶ÊV
f°vBxt‚U!Hþ±(#

ž"‰Qr£ƒƒBþ_¨R8f
ý“ƒR9f
"bÀ_TxR?g~~Bd”]r£{{BþXBYBRBs



ÕA™œìBþsÕ?›šAÖ

ÖAš›

CÓÔ

B
"q2BNþÙ4?CN6ˆ:aCf?m;õU 
B:Š>Ì6	
ì
­þðÔYËýbù¢€>BÅg
lùbýËYԐh_gÅB>
“;”ïéþg™B”þl
éþg™B”þl
4\p
k^4
;J“þ. þ¤/!is(!€u™
$"-E%	)"!	xE==EwX[)G0+>>
':>>+0G)

"@nHDc60IE)/O4*n)c¦–¤vM at AH6]8RaHl—ƒH^¢¯gaR8]6HA at Mv¤–¦c
=?[Z@@Z[?ýàEdcFGef
ÇA+)$;;'V,()6þoRvvRQww
0*6
1Ž(8B/2
1Ž(8/2/
0,4.2
Z7U)%-B"!(
Z7U)%-B"!(B 1)X0f
"”L
,N›
v*	#M›c"!(!"
’5-¤
1Ž(8£‰¤-5’
Z7U)%-¥þ“†"!(
f0X)1 ¥T¥	-%)U7Z^(!"
Z7U)%-B††"!(
f0X)1 ‘‘B†B	-%)U7Z^(!"
"”L
¥¥,N›
v*	¥¥#M›c"!(††!"(
Z7U)%-þ¨†"!(
f0X)1 ‘+	-%)U7Z^(!"
1D:4†½B•l41!}ps
Ž1
/2/iµµ„´;{sp}!14l•B½†4:
/2/iµ
1Ž%;´„µµi/2
I/lh)+O#9~S>"àzc³6#
o¦?>L#q4f»
PW—n[~9#r
#6³czà">»f4q#L>?¦
#6³]yÚ¢[~9#O+)hl/I
OX–
z/I
OX–o[~9#O,(h
o¦?>L#l3eµ>"Úy]³6þðÿ
Iþí2
#6³]yÚºh2hh2h¢[~9#O+)hl/I
OX–Ñ]]2]]
	 v¥E9	(‰Q~²Kü72¤_™ÙbS=<m]+3OBM²~N‡
2©c™Ù[M$?K²~Q‰(	9E¥v=<=SbÙ™_¤27)‡N~²MBO3+]
+Bq¶-±5þÿÏ]r

" iý”"0þMÍ%!1^*# ##.Y*'þÒ#-þL
$11,%jY%
	]Xeþ3%F+
#C5[	$Ye>*8 	]qr]
%Yj&+11$
Z(¤ÏF#
+Bq]	 8*>eY$	[5þÿÏ]r
Z(þ½ÉAaeX]	
ƒþÛÏF=$	[5C#
+Bq]	 8*>eœÏ]
$11+&jY%
	]XeaA*þÛFúþ½5[	$Ye>*8 	]qB+
#Br]
+Bq]	 8*>eþdÏ]r2Yj&+11$
Z(þ½ÉAaeX]	
þXÏF
#þ½5[	$Ye>*8 	]qB+mr]ÌAþ7C(Z
$11+&jY%
	]Xeaþ”%F
#C5[	þ÷Ïr3%
	]XeaAÉþ½(Z
$11,%jžÏþÛ
+Bq]	 8*>eY$	[5C#eÏ]mAaeX]	
%Yj&+11$
Z(Cþ7%ÏF



Êr¢ré¨8Ha;

?××þóBþí


A××

A×Õ
AÖÖ
¯˜(!
+6b
1aA0L 9puU%8U(.^\S0?P"<+<Z<>46$,P?'0F6.M66a<OfK:7<"U0'?"K5Aoi(&=YY0)%~"/#)†e‰F@*,&Xjq*L8,"=P6V	kG7;\&
”–>'0Û¥
9IMgIIH“vJuV~äŠL
$)57+1TÂyM`"F2^|T+*B=#Lt•Ê<+ž/ve?OÞ«}`$CbDk&+!V?K0D8j!©”H3(3i–MHd?0–c)7ùq¦G;k¨;
9IMgII_¾;*0R &uV~äŠL
A;uOOu2lD)·~!$(3[‘Û‘2ŸÂþÓP™z¸WþÙþÁ\NH–‰S<R5++	kG7;\11sÎÍipiID*M&wÎ
F7~;X|D]-*X&O49DwVYléÆ ±š—MA:BPI31 MOA)* 6ƒg›f”XE*5j=FD:Pmq
>C8•C[8IFA`'"Q5#	;*n7q¹0%y;2-2OFZG
3FB¨+-35)4e,f':Q
8	7,3!BŒ?)4cC7s#UCWƒí&	\K}^à4,mc9PC[ 	 hn3*@ÐX©^ci
=;4˜BX$("7‹\4Ÿánm?6.-EI`O'!
‚¡.e
>C8”B$*¬Ì0)q2W);N@[¯ý²¬_Z(=<81.5OFZ''Dþ¾)TI}@)I<ž
ECI #Z` þK_W+'?fmOþÙ(&N
"?'þó79E]` Éþ"?3+0 Š+"$þ( 
 2>QtY=P
PWDF†h)tg"&1`Wþ&"?A˜ê±Q$ì¯V.b‹ž!#'‰!1N˜&
PWHI†g!(*gXý%#<D
Êœ5V.b‹ž!0C¤'*
QVHž03G²//#ÕC(ɝþú'.a
@A9•<$0Dþç—6_nV=“`ÊF‹+X,	6:7:/03OEZ! 0ýó"÷º*Ÿ}>£
PWHxÖ+"T-70À…Z2bä )0	J$
!HV'fgGOHþ}M`bX*';i\2uI„„
PWKŸGÇUN "a¾]X'  †-+5V~.bþÙ''ä±þ×* O
PWKž3€7- %Ò	lj2/<f\,bþÙ$"hi$$8U2$`
PWKžc€bKž3€7- %Ò	lj2/s2/<f\,bþÙ$"å°þÙ$"hi$$8U2$`
ˆ¥,
PWKŸ!GÇUþù79E]¨þ¸gC"r+0 ±†-+5V~.bþÙ'' ä±þ( 

2G%²M9tþÓýº!
þ~ù0þ>­S…€
·
ÕÎ
	ª"
c., with final additions and corrections provided by Coen Hoffman, Elsevier (retired)
i
o


 !"#$%&'
 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^

 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopuni00A0uni00ADuni0100uni0101uni0102uni0103uni0104uni0105uni0106uni0107uni0108uni0109uni010Auni010Buni010Cuni010Duni010Euni010Funi0110uni0111uni0112uni0113uni0116uni0117uni011Cuni011Duni0120uni0121uni0122uni0124uni0125uni0126uni0127uni0128uni0129uni012Auni012Buni0130uni0132uni0133uni0134uni0135uni0138uni013Duni013Euni013Funi0140uni014Auni014Buni014Cuni014Duni0150uni0151uni015Auni015Buni015Cuni015Duni0165uni0166uni0167uni0168uni0169uni016Auni016Buni016Euni016Funi0170uni0171uni0174uni0175uni0176uni0177uni0179uni017Auni017Buni017Cuni017Funi0180uni0188uni0190uni0195uni0199uni019Auni019Buni019Euni01A0uni01A1uni01A5uni01AAuni01ABuni01ADuni01B5uni01BAuni01BBuni01BEuni01C0uni01C1uni01C2u!
 ni01C3uni01F0uni0221uni02B9uni02BAuni02BBuni02BCuni02BDuni02BEuni02BFuni02C2uni02C3uni02C4uni02C5uni02C8uni02C9uni02CAuni02CBuni02CCuni02CDuni02CEuni02CFuni02D6uni02D7uni02DFuni02ECuni02EDuni02F7uni0300uni0301uni0302uni0303uni0304uni0305uni0306uni0307uni0308uni030Auni030Buni030Cuni030Duni030Euni030Funi0311uni0312uni0313uni0314uni0315uni031Auni031Buni031Duni031Euni031Funi0320uni0327uni033Funi0359uni035Cuni0360uni0361uni0362uni037Euni0384uni0385uni0387uni0391uni0392uni0393uni0394uni0395uni0396uni0397uni0398uni0399uni039Auni039Buni039Cuni039Duni039Euni039Funi03A0uni03A1uni03A3uni03A4uni03A5uni03A6uni03A7uni03A8uni03A9uni03AAuni03ABuni03ACuni03ADuni03AEuni03AFuni03B0uni03B1uni03B2uni03B3uni03B4uni03B5uni03B6uni03B7uni03B8uni03B9uni03BAuni03BBuni03BCuni03BDuni03BEuni03BFuni03C0uni03C1uni03C2uni03C3uni03C4uni03C5uni03C6uni03C7uni03C8uni03C9uni03CAuni03CBuni03!
 CCuni03CDuni03CEuni03D0uni03D1uni03D2uni03D5uni03D6uni03D8uni
03D9uni03DAuni03DBuni03DCuni03DDuni03DEuni03DFuni03E0uni03E1uni03F0uni03F1uni03F4uni03F5uni03F6uni0401uni0402uni0403uni0404uni0405uni0406uni0407uni0408uni0409uni040Auni040Buni040Cuni040Euni040Funi0410uni0411uni0412uni0413uni0414uni0415uni0416uni0417uni0418uni0419uni041Auni041Buni041Cuni041Duni041Euni041Funi0420uni0421uni0422uni0423uni0424uni0425uni0426uni0427uni0428uni0429uni042Auni042Buni042Cuni042Duni042Euni042Funi0430uni0431uni0432uni0433uni0434uni0435uni0436uni0437uni0438uni0439uni043Auni043Buni043Cuni043Duni043Euni043Funi0440uni0441uni0442uni0443uni0444uni0445uni0446uni0447uni0448uni0449uni044Auni044Buni044Cuni044Duni044Euni044Funi0451uni0452uni0453uni0454uni0455uni0456uni0457uni0458uni0459uni045Auni045Buni045Cuni045Euni045Funi0462uni0463uni046Auni046Buni0472uni0473uni0474uni0475uni0490uni0491uni2010uni2011
figuredashuni2015uni2016uni2017uni201Buni201Ftwodotenleaderuni2031uni2032uni2033uni2034uni2035uni2036uni2037uni2038uni203Euni2040uni2043uni204Euni2052uni205F	nsuperioruni20ACuni20D0uni20D1uni20D2uni20D6uni20D7uni20DBuni20DCuni20DDuni20DEuni20E1uni20E5uni20E6uni20E7uni20E8uni20E9uni20EAuni20EBuni20EEuni20EFuni20F0uni2102uni2107uni210Auni210Buni210Cuni210Duni210Euni210Funi2110uni2111uni2112uni2113uni2115uni2116uni2118uni2119uni211Auni211Buni211Cuni211Duni211Euni2124uni2125uni2126uni2127uni2128uni2129uni212Buni212Cuni212Duni212Euni212Funi2130uni2131uni2132uni2133uni2134uni2135uni2136uni2137uni2138uni213Cuni213Duni213Euni213Funi2140uni2145uni2146uni2147uni2148uni2149onethird	twothirdsuni2155uni2156uni2157uni2158uni2159uni215Auni2190uni2191uni2192uni2193uni2194uni2195uni2196uni2197uni2198uni2199uni219Auni219Buni21A4uni21A5uni21A6uni21A7uni21AEuni21B0uni21B1uni21B!
 2uni21B3uni21B4uni21B5uni21B6uni21B7uni21B9uni21BAuni21BBuni21C4uni21CDuni21CEuni21CFuni21D0uni21D1uni21D2uni21D3uni21D4uni21D5uni21E0uni21E1uni21E2uni21E3uni21E4uni21E5uni21E6uni21E7uni21E8uni21E9uni21F5uni21F7uni21F8uni21F9uni21FAuni21FDuni21FEuni21FFuni2200uni2201uni2202uni2203uni2204uni2205uni2206uni2207uni2208uni2209uni220Auni220Buni220Cuni220Duni220Funi2210uni2211uni2213uni2214uni2215uni2216uni2217uni2218uni2219uni221Duni221Euni221Funi2220uni2221uni2222uni2223uni2224uni2225uni2226uni2227uni2228uni2229uni222Auni222Buni222Cuni222Duni222Euni222Funi2230uni2231uni2232uni2233uni2234uni2235uni2236uni2237uni2238uni223Buni223Cuni223Duni223Euni223Funi2240uni2241uni2242uni2243uni2244uni2245uni2246uni2247uni2248uni2249uni224Auni224Buni224Cuni224Duni225Duni2260uni2261uni2262uni2263uni2264uni2265uni2266uni2267uni2268uni2269uni226Auni226Buni226Cuni226Duni226Eun!
 i226Funi2270uni2271uni2272uni2273uni2274uni2275uni2276uni2277
uni2278uni2279uni227Auni227Buni227Cuni227Duni227Euni227Funi2280uni2281uni2282uni2283uni2284uni2285uni2286uni2287uni2288uni2289uni228Auni228Buni228Funi2290uni2291uni2292uni2293uni2294uni2295uni2296uni2297uni2298uni2299uni22A2uni22A3uni22A4uni22A5uni22A6uni22A7uni22A8uni22A9uni22ACuni22B2uni22B3uni22BAuni22BEuni22BFuni22C0uni22C1uni22C2uni22C3uni22C4uni22C5uni22C6uni22CEuni22CFuni22D5uni22DAuni22DBuni22DCuni22DDuni22DEuni22DFuni22E0uni22E1uni2300uni2305uni2308uni2309uni230Auni230Buni2312uni2316uni2320uni2321uni2322uni2323uni2329uni232Auni233Funi2340uni2393uni239Buni239Cuni239Duni239Euni239Funi23A0uni23A1uni23A2uni23A3uni23A4uni23A5uni23A6uni23A7uni23A8uni23A9uni23AAuni23ABuni23ACuni23ADuni23AEuni23AFuni23B0uni23B1uni23CEuni23D0uni23DCuni23DDuni23DEuni23DFuni23E0uni23E1uni23E4uni2605uni2606uni2609uni263Cuni263Duni263Euni263Funi2640uni2641uni2642uni2643uni26!
 44uni2646uni2647uni2648uni2649uni26E2uni279Buni27F5uni27F6uni27F7uni27F8uni27F9uni27FAuni27FBuni27FCuni27FDuni27FEuni2902uni2903uni2904uni2906uni2907uni2912uni2913uni2934uni2935uni2936uni2937uni2938uni2939uni293Auni293Buni293Cuni293Duni293Euni293Funi2940uni2941uni2981uni299Buni299Cuni299Duni299Euni299Funi29A0uni29A1uni29A8uni29A9uni29AAuni29ABuni29ACuni29ADuni29AEuni29AFuni29BBuni29BFuni29C0uni29C1uni29E3uni29E7uni29FAuni29FCuni29FDuni2A00uni2A01uni2A02uni2A03uni2A04uni2A05uni2A06uni2A09uni2A0Auni2A0Buni2A0Cuni2A0Duni2A0Euni2A0Funi2A20uni2A2Funi2A3Funi2A42uni2A43uni2A7Duni2A7Euni2A95uni2A96uni2A99uni2A9Auni2A9Buni2A9Cuni2AFDuni2B50uni2B51uniA727u1D49Cu1D49Eu1D49Fu1D4A2u1D4A5u1D4A6u1D4A9u1D4AAu1D4ABu1D4ACu1D4AEu1D4AFu1D4B0u1D4B1u1D4B2u1D4B3u1D4B4u1D4B5u1D4B6u1D4B7u1D4B8u1D4B9u1D4BBu1D4BDu1D4BEu1D4BFu1D4C0u1D4C1u1D4C2u1D4C3u1D4C5u1D4C6u1D4C7u1D4C8u1D!
 4C9u1D4CAu1D4CBu1D4CCu1D4CDu1D4CEu1D4CFu1D538u1D539u1D53Bu1D
53Cu1D53Du1D53Eu1D540u1D541u1D542u1D543u1D544u1D546u1D54Au1D54Bu1D54Cu1D54Du1D54Eu1D54Fu1D550





€
š,,::HNd~°Êäþ







Index: modules/damieng/graphical_editor/daxe/lib/fonts/STIXSubset-Regular.ttf
+++ modules/damieng/graphical_editor/daxe/lib/fonts/STIXSubset-Regular.ttf


Ò
M


¼
¼
}
.ÿþ
Ò
ê

ô
ð
ù


'?Y\`~„‡‘£ÐÕØðôQ^bjr  " % 0 > @ C N R _  ¬ Ð Ö Û á å î!!!
!!!$!+!<!E!S!!¤!®!°!¹!Ä!Í!à!õ!÷!ý""";"]"`""¢"¬"²"º"¾"Î"Õ"Ú#





H
p
®
â,‚¼äü
 4BV¦àR‚¶"`Ê0ŠÆð0hžâBvÂNr¤°â8ž²Ô".<b€ªæú4h|¢Àæ$^¨
Z–ÒX„²Þ\¸î$X äJŽÒ  d ² ò!N!Œ!ä"*"Š"Ò#6#x#¶#ô$0$t$¶$ô%2%v%Ò&&V&š&Ò''^'¬("(t(ð)J)–)Þ*,*p*²*ò++D+v+–+à,D,|,²--V-”-Ì-ü.0.\.¢.ê/ /P/˜/Ú020‚0Ø1(1|1Ê22 at 2r2Î3$3h3¦44V4¬4ü5P5¨5ì686Œ6Â6ò7*7^77Ò88f8°99P9z9Ä::J:ˆ:Î;
;V;Œ;¼<<H<|<ˆ<š<º<Þ==x=Ž=²=Ö=ú>>:>V>h>z>Œ>ž>°>Â>Ð>Þ>ò??? ?2?F?Z?f?~?”?º?Ö?ü@@6 at H@\@„@˜@¬@¾@à@î@üAA(AJAnAŽA A¬A¾AÞAôBB<B`B„B”B®B¾BÎBâBîCC(CC²CÐCôDDFD\DŠDžDÔEE<EREŽE¶EöFDFfF²FèG"GZG˜GÈHH>HdHŒHÀIIdI¶IîJ,JzJÂKKXKˆKàLLlLšLâM.M~M¬MäNNBNtN¨NÎO.OZOšOÊPP at PjPšPÞQQDQ„QÀR
RDR‚RÐSStS¸SôT>T|TªTîU.UZUpU®UÄV
m at m„m´mðn*nxnÄoojoÆppXp pðq<q‚qÀqòr(rTr~rŠr–r¤r²rÀrÎrâröss:s\s|s¸söt2tlt‚t¤tÔu`vv&vJv|v”v¸vêvüww at wNwhwtw„wüx(x(xpx®xÂxÖxâyy"yPyŒy´yÊyúzzz,zZzlzœz´zÔzò{Z{Ž{Ü|D|Ð}:}^}œ}ê~H~º.l’€€v€¨
:lžÐü(T€Êò‘‘D‘l‘”‘¾‘î’’r’¼““L“œ”
žžHž|žˆž¢ž¶žÚžîŸŸ"ŸBŸ|Ÿê Œ ô¡¤¢Ž¢ú£~¤
­N­n­Ž­Ì®
®(®F®r® ®Ú¯¯F¯x¯”¯°¯â°°8°\°–°Ô±
Ë&ËBË^ËzˬËêÌ.ÌZ̜̰̈ÌÒÍ ÍŠÎ`ΦÎòÏ:ÏZÏtϦÏÌÏòÐÐ.ÐLÐjЌЮÐÔÐúÑÑ8ÑPѐÒ

,	86!+8	! /)2!-&	
S:!".=8331&.V
[ý«!
.X24




S:"".<ª. ! ý¨64	4 1(/R
A××
/yLp sYwƒ¦{<F6B(/:S
ÓN&7EM¦Š $:
ö$8þJ5$

LGñV‡$-G#€GšQ#+H"†


„\$"&!B0II71F/ 16--	0 -+/¿BP ".&'
f9(;"%'!8(;©þÏ&.~^nŒ)þ%	þ“ô+.Vb7//
YHY1)B7/2þB$!!(5

+SÂE
1"3ð0?UÊ\6(:á+	&ú!"
	3-")-?4)2K		ID* .CN:A
ˆD7""'
0IX5&;eeML-Ut þâ0*
.ž"2RVF:û%þ½"-ç'þ©#
x Ä	fc/Â2þ¿2L%ÿ
¥#*D%		*xÄ<
–'O9 &*„(Í pa.ÂþgjW!,JXú		
‡/!Zþó…Tˆˆ•!7vþkB

	2/-$,Q*2%E3/'
'"2W		$

ŒþÙ34?O

þþ³³ýN²þjþ7þ­

þþ³³ýN²¥=&<M ('+'/ 3,0#4


uBJ:1ýÛ
„\$"&!û` ý³0II71F/ 16--	0 -+/¿BP ".&'
„\$"&!û” aþG0II71F/ 16--	0 -+/¿BP ".&'
„\$"&!ûgg§ý 0II71F/ 16--	0 -+/¿BP ".&'
„\$"&!~91.14ýà0II71F/ 16--	0 -+/¿BP ".&'
„\$"&!P((ýÔ0II71F/ 16--	0 -+/¿BP ".&'

„\$"&!ŽR::*):c&'&&ýø0II71F/ 16--	0 -+/¿BP ".&'
3+Aå¦%)+(U[:!!JšT ( &*;0%$?1)
.>VaKB,ÎB88¬4!2-&1$;
yY€E3=*.`NVm)
QA2B;&5A0tÏqUa|giŠ$8O(A@))2þgW@)VS5c
.ž"û` ý£RVF:û%þ½"-ç'þ©#
.ž"û” aþ7RVF:û%þ½"-ç'þ©#
.ž"ûgg§ýRVF:û%þ½"-ç'þ©#
.ž"P((ýÄRVF:û%þ½"-ç'þ©#
–'O9 &*„(Í pa.û” a9þgjW!,JXú		
Y7D#8B{ F§þÕPrcm”š0(¸!þ¡ò-kT».
–'O9 &*„(Í pa.P((¬þgjW!,JXú		
„\$"&!û6þ0II71F/ 16--	0 -+/¿BP ".&'
„\$"&!˜SJa+ýà0II71F/ 16--	0 -+/¿BP ".&'
##& ~\$"&!I)#'#1#;I71F/ 16--	0 -+/¿B% 'ì ".&'
þh#4@Cw`j•+i'xþ5æ(<fU\o 
ÓN&7EM¦Š $:
YHY1)B7/2þB$!!(5
ÓN&7EM¦Š $:
YHY1)B7/2þB$!!(5
S:!".=b0ÈF83W“¦aV
ÓN&7EM¦Š $:ý†8331&.V
í4š1~91.14ýž8ã#7þ¡8

(+ñ0/ñ.-,&. /E,UP0¢%'1'
	(i>#'I${?

	3-")û” aÎ?4)2K		ID* .CN:A
ˆD7""'
	3-")ûgg§þ‹?4)2K		ID* .CN:A
ˆD7""'
.ž"~91.14ýÐRVF:û%þ½"-ç'þ©#
.ž"û6þRVF:û%þ½"-ç'þ©#


.ž"ŒR::*):c&'&&ýêRVF:û%þ½"-ç'þ©#
.ž"û”`”`þ7RVF:û%þ½"-ç'þ©#
–'O9 &*„(Í pa.ûgg§àþgjW!,JXú		

‡/!Zþó…Tû” aþˆ•!7vþkB

‡/!Zþó…T<þ6ˆ•!7vþkB
f»»9(5M6&(;ý…&.~^nŒ)Å%	¿ôQa;D+/
$,$aQKt0
		ú[
/Â6°,!39l6Zþ6
$&72&*\5	,eeM< $5<›-		9%t þâ!

ƒ°Ô ¾
ƒ›]
ƒ°Ô ¾Ô ¾
Z/);
Ç

;*)9;*('&&'ŒR::*):c&'&&
û‹™

(š"û`”`
Z/);
:		PR
=
	5
	?N		N:	<P

O8
3IcJJJcI3LjSDSjLL(<  <(**

S:"".<ª. ! ý¨64	4 1(/R
)þú.	!%Ç*&.Îö#%þüts,^r5nèýÍ4$îþí

j.ƶ‘’µµ$µr0+8CU: %Z1KN3Sì"%2D2!:,$L.š¿Àœ˜¾ÃU‡$/C$…H¶S-0P
ö$8þJ5$


'®!N*;	-Fû„ Qþ†;Pn68ƒfcŠU=ˆþù.YœàOP"LX9
4 6M25(9($1	-+)04
ÿ'Ä–
1"þ‡
'®!N*;	-F;Pn68ƒfcŠU=ˆþù.YœàOP"LX9
*(	(-1`=^§,%"/ý6ZA&*Dþt,fN>V143TG
ÿ&¼É
4 6M25(9($1	-+)04
$ 12(L›1< 8«)%IDC/šPšEI@^.# $	ÒZŠW(#?
ÿ'ÄÌ
1"þ‡
#A3­-!
ŸãU$ÈCW%)6Ê 
–]2)
$ 12(H¡<2*#!«&+4!(''/	N5s‰D_-$ $	­=l$53 ?

%/‰5^G3^ D>RA;!$8w]#KBTFNG1
$ 12(LHSžZHM`SN;CEI@^.# $	Jfx®A
1"þ‡
$ 12(LEVÉšêvj	8"2EI@^.# $	FL•</
1"H4#>7É¿‹*
1"H4#>7É¿
\‚}<<ŒˆgIWþß
¬©t.¤S‡§_c/-†‰bc$"ca
×A69ˆ%IM:%ƒm^ˆZ=E**˜U‚PO?pP%?@

-3IÃÃG<Uqsˆ[b‘‡'r> Oq
-3IÃÃG<UQsˆ[b‘‡'r> Oq
ÿòº–
	'/9 #`i¹nX`Ua#9Øþ¼%?+3 )+.A07%!9Þó;R“
Ó8)B¤8" ;º
2&$FH#%'%#}3.D"$
(
P¦,&#\þþP&	
+$;B(8'<=&@;$+
/#'6#/&/!†<	Ó9:ÓH†!,G34HC##&$'HF,17»:! ;º	&1
HF'$&##CH43G,
"W6FlZì!\M at W
Ó8)B¤8" ;º
2&$FH#%'%#}3.D"$
(
	'/9 !8%AÌþ¼%?+3 )+.A07%!9þK5#
ö$8þJ5$

P¦,&#\þþP&	
%@Œ1%;±þî*
9  9!9É.=]|;  ;sB5
Ü.#AþX9!
0—1	:£ 7;:!9µ5#(Aþ*â6 %@þ)à7$#@þV: 
aüø8!!8
0—1	:£ 7;:™#e!9µ5#(Aþ*â6 %@þ)à7$#@þV: 
J_+2(74>5"67?[rfiˆšm-]kR6]	yÞr at 2UJ€M2c
KZ&Ð))m5<3þÄ*5
|#~	)H
6(3296
"9!$$(7$¤'ž6#~0
@(3376
39&Ð)82
˜5<5þÂ*75<
)@T('N.#Ž
KZ&Ð))mü” aÇ<3þÄ*5
	3-")-?4)2K		ID* .CN:A
ˆD7""'
«

"9!$$(7$ü” aþ¤'ž6#~0
@(3376

!u'(Ð&b*&Â'þÜ,<11<,%&!þÖ2,

("87?çiˆ‡gh€]V82UJ
,A
,AY;2c
u Ä	crÛ{""þë0/I(ÿ
KCj›ÝÌ
ŠÐÐð
,	86!+8	! /)2!-&	
8A=A3<)·Yú
$Õ!CGGAÉ	
M;›…"&"y–
jj)

jj)

F?
jj)
6)
:		PR
=
	5
×	?N		N:	<P

O8
j¢R:/n(#$’O9'(%A(9m4G.*8Mb!ãXeRBC
@A9•<$0<6_nV=“`Õ79E]¨þ"r+0 œ:7:/03OEZ! 0<*Ÿ}>£þs( 
	1;1%L.+9JY·‡2!:M>V)•hJ\.'‚&>8	ˆ@ OND Z„68-%-_T/!“A
CAd2#IK'!5
u8Y",G>2lsÕ"+#<!7.5,4þþÖX"%³%
dª9yG3)?'"<Wp
	P0<#S4.¢,3O:805PAOwöHV)Ln(!;2&W/S7'!B.E#7G
ŽW*N3Nic, g!"\þ+!
	þ¼015$
	(+´3D\\BC^^DŒ‹ˆ©W~¼#A)5 )+.A0þ÷ç#A)5 )+.A0?

þ~ù0þ
B@5­S…€£%%
0	"9G5V@^(,%x"es/#!=70©–H3(3h—‹–10–c)7õk¨G;k¨;¥@&
#,5	X
à¯A¥þ°Nþñ–_eC_?þåN/bò"þÑþñçã>ýÂ
=/VE

E5*
24&5
WF9@$6"J)2N=VgIœ@‹Ü™Wt\g‘,'(R%65 ;?)_s%/!"LM`MYR³gureIs
9F)Œ]?lK4'8X5<#'0G 8bAs¦+.'"1/2<5R#D/E$14!96þ,Oj-Kck6'UYG-%Om:>qd;
~BA€%¾
J°,3qLEÉ’Ë-T†y'07(v	
,BHXnQd…?dÈK*XkNB^7#0'5]mIBDœf[}~okiTKIHœ
v¬’>15=Aj7I>q
8xB1F<&/"(O?R`?4DlYPO{
OC9Qc|Ce
T–I<NA88WYÍJ=N6 at 88!dT-&!%	þæ96 %$`6F6Ef
d·'%P8YLa0 áþ°-#„’,&•ž¾½&6& XÛÄJŠW"c5K)2
-!% f…ò¶þÈY-.1 ‰`}Q?
Dn—G'0""$[25Š\8|6&ßÎ;:.-^1&>D”3}š,%\d"4
	49:8&/">*cDQ&3-$	þåÊ2B#&029-	$
AC¥;e Š$(‹#7%(>)k1gRFHE*::%"%
.{A!*¥	".#? =–,+N0,!’0"-)
.=
^PKO"—^4-2R,#I"Ë€p{m>]I/L[;lz&@aƒ~gý—'?i
Teþ>1Åþ´´ 	k”@ .(FS ,&-›ýN²þjþ7þ­
T@þ>1Åþæ'ûeN)6"B>:D&0Tq(”@ .(FS ,&-›ýN²þ¿UlT6 ,J@7=0%C/Q
T@þ>1Åþ}+,hS(3%&/*4+8'?-.>”@ .(FS ,&-›ýN²¥=&<M ('+'/ 3,0#4
T@þ>1Åþð7F´Ë/7}‡”@ .(FS ,&-›ýN²þÅZZ1
þþ³³
TŸxL?>QcJrŽþÎ-L,4K-YýN²@ .(FS ,&-›
1Ž(8
1¨6ýŠw3|qs
1Ž(8B1/
1Ž(8B1e|qs{6vý‰3
1Ž(81//
1Ž(81/
2(81//
2ì8ý¾4ztt{6B3|qt
%\~88Fm/“M16‚X%



ý0lF88}–X‚61Mým/‹



•%


‹/ýmM16‚
qgýu


%X‚61M“
0,5¤¤
//1¢¢B¤¤5,
0,5¤
1Ž(8£š2C3|qs{6ý¾
1Ž(8£‰¤-5’
1Ž(8¢‡dý½3|qs{6B2
2Œ%:¢¢1//
0,5¤¤-5’
6€7
41&ýª˜7€
41&T
6€7–ý¬&1
//1ýÓo8h{sq|32BýŒ6
r|{r@B(ƒ5
3„=T
5/1ˏŒÈÀs¡¡r2 n€n3ÉÉ
7~.2¢æ¢BÈŒË1n€n 2r¡¡sÉɍ3
36B3#€þf3#€4
36
Z7U)%-¥¥B††"!(
"”L
‚‚,N›
v*	ƒƒ#M›c"!(!"(
f0X)1 ¥¥B†B¥¥	-%)U7Z^(!"
Z7U)%-B"!(
Z7U(*!þ
†;:ýyö *)h?‹yIf
f0X)1 B(!"B	-%)U7Z
f0X)1 B(!"B!ç)fIy‹?h)* öýy:;†þ
!
"”L
,N›
v*	#M›c"!(!"
f0X)1  1)X0f
Z7U)1!!¢"!((!£)fIy‹?h)* B *)h?‹yIf)*!þ¾!­b;:ýœ:
1Ž(8
1BBBBBB¨6op3|qsþ›kþèkþèk
//1B8(ŽWBBBBB
//1B8+kþèkþèkÖ{sq|3po6
)X0f
Z7U)
›Ø¦t~,€u
›þö›
)X0f
Z7U:~,€u¦(þP°)h?‹yIf¥:¥þP°
f0X)
›
›
)U7Zã€,~t¦
)U7Z
f0X)
›
Bt¦u€,æ)fIy‹?h)°þP2°þP¥:
1ÅŽ1
/*6B8¨6ý”m3|qsþC{sq|6mý”6
0,5¤¤
//1¢¢B¤¤5,
2Œ%:¢¢1//
0,5¤¤-5’
0,5¤¤¤¤
3‰F’p
ý´[NÜž:h
?€žx­'.6
ýæ
3‰”[N
?€žBž:h5­'.6
þ¢
3‰F’p
^
3‰”[Nþ/[N
?€žž:h
?€žž:h5­'.6š­'.6

J`Þo–ýj–ÇÇþýþÔ
>.ã.?
9&W[#"þ""Z
&<¼*@jCEC—jE=F!ƒ%IpC6Wqþü1ht[;\Fm¯0'MhþÁ,*µkbvT7
ªkIB[5
B’þ‡[5Ò´k

rt

X *M
Q%;'pp'T!!U'ts'Q


#C5±*¹qr]
°þP5
þíA××ýË5þP°

Mn;1E
Mn;1Ü‚Ú"WZžpE]„‘#?A'"QT¨oE]„þdnM7\nM7\
1þR/UF-"QTÅnM]„
Mn(F! ;E	Mn;0Ü‚#CH,-"WZ!I	"TL]„’/UF-"QT©pD]„þdnM
8nM7[

Mn
	#E	Mn;0݁#CH,-"WZ%,,]„’/UF-"QT©pD]„þdnM%".L*nM7[






++06=[HŠK/6üU€miFNU at VÖ”U

ÀBþöB

A××ÙB


A××ëBþ÷B

A××þMLLB†BLLB†
AÖØ?
AÖØ
þí?ØÖAþï
¥ðÏMAa‹+T6
ªäÄIAZÔ€?‡ÝÈ\@
Øl-o
A××wJ‚@@N<Q{@@
1<!YF>ùK26*¬Â:üþô=S@XÒ°…2J‚B±ÇN<Q{ßy
¡µ—TAaÐ}?Û9"/WC
“ÌgØ7
óA·ºèó?¹¸Aô
ôA¸¹
“|QCT·cBj•uæMAþ£?‹
h¯wByrƒC‹¶ÊV
f°vBxt‚U!Hþ±(#

ž"‰Qr£ƒƒBþ_¨R8f
ý“ƒR9f
"bÀ_TxR?g~~Bd”]r£{{BþXBYBRBs



ÕA™œìBþsÕ?›šAÖ

ÖAš›

CÓÔ

B
"q2BNþÙ4?CN6ˆ:aCf?m;õU 
B:Š>Ì6	
ì
­þðÔYËýbù¢€>BÅg
lùbýËYԐh_gÅB>
“;”ïéþg™B”þl
éþg™B”þl
4\p
k^4
;J“þ. þ¤/!is(!€u™
$"-E%	)"!	xE==EwX[)G0+>>
':>>+0G)

"@nHDc60IE)/O4*n)c¦–¤vM at AH6]8RaHl—ƒH^¢¯gaR8]6HA at Mv¤–¦c
=?[Z@@Z[?ýàEdcFGef
ÇA+)$;;'V,()6þoRvvRQww
0*6
1Ž(8B/2
1Ž(8/2/
0,4.2
Z7U)%-B"!(
Z7U)%-B"!(B 1)X0f
"”L
,N›
v*	#M›c"!(!"
’5-¤
1Ž(8£‰¤-5’
Z7U)%-¥þ“†"!(
f0X)1 ¥T¥	-%)U7Z^(!"
Z7U)%-B††"!(
f0X)1 ‘‘B†B	-%)U7Z^(!"
"”L
¥¥,N›
v*	¥¥#M›c"!(††!"(
Z7U)%-þ¨†"!(
f0X)1 ‘+	-%)U7Z^(!"
1D:4†½B•l41!}ps
Ž1
/2/iµµ„´;{sp}!14l•B½†4:
/2/iµ
1Ž%;´„µµi/2
I/lh)+O#9~S>"àzc³6#
o¦?>L#q4f»
PW—n[~9#r
#6³czà">»f4q#L>?¦
#6³]yÚ¢[~9#O+)hl/I
OX–
z/I
OX–o[~9#O,(h
o¦?>L#l3eµ>"Úy]³6þðÿ
Iþí2
#6³]yÚºh2hh2h¢[~9#O+)hl/I
OX–Ñ]]2]]
	 v¥E9	(‰Q~²Kü72¤_™ÙbS=<m]+3OBM²~N‡
2©c™Ù[M$?K²~Q‰(	9E¥v=<=SbÙ™_¤27)‡N~²MBO3+]
+Bq¶-±5þÿÏ]r

" iý”"0þMÍ%!1^*# ##.Y*'þÒ#-þL
$11,%jY%
	]Xeþ3%F+
#C5[	$Ye>*8 	]qr]
%Yj&+11$
Z(¤ÏF#
+Bq]	 8*>eY$	[5þÿÏ]r
Z(þ½ÉAaeX]	
ƒþÛÏF=$	[5C#
+Bq]	 8*>eœÏ]
$11+&jY%
	]XeaA*þÛFúþ½5[	$Ye>*8 	]qB+
#Br]
+Bq]	 8*>eþdÏ]r2Yj&+11$
Z(þ½ÉAaeX]	
þXÏF
#þ½5[	$Ye>*8 	]qB+mr]ÌAþ7C(Z
$11+&jY%
	]Xeaþ”%F
#C5[	þ÷Ïr3%
	]XeaAÉþ½(Z
$11,%jžÏþÛ
+Bq]	 8*>eY$	[5C#eÏ]mAaeX]	
%Yj&+11$
Z(Cþ7%ÏF



Êr¢ré¨8Ha;

?××þóBþí


A××

A×Õ
AÖÖ
¯˜(!
+6b
1aA0L 9puU%8U(.^\S0?P"<+<Z<>46$,P?'0F6.M66a<OfK:7<"U0'?"K5Aoi(&=YY0)%~"/#)†e‰F@*,&Xjq*L8,"=P6V	kG7;\&
”–>'0Û¥
9IMgIIH“vJuV~äŠL
$)57+1TÂyM`"F2^|T+*B=#Lt•Ê<+ž/ve?OÞ«}`$CbDk&+!V?K0D8j!©”H3(3i–MHd?0–c)7ùq¦G;k¨;
9IMgII_¾;*0R &uV~äŠL
A;uOOu2lD)·~!$(3[‘Û‘2ŸÂþÓP™z¸WþÙþÁ\NH–‰S<R5++	kG7;\11sÎÍipiID*M&wÎ
F7~;X|D]-*X&O49DwVYléÆ ±š—MA:BPI31 MOA)* 6ƒg›f”XE*5j=FD:Pmq
>C8•C[8IFA`'"Q5#	;*n7q¹0%y;2-2OFZG
3FB¨+-35)4e,f':Q
8	7,3!BŒ?)4cC7s#UCWƒí&	\K}^à4,mc9PC[ 	 hn3*@ÐX©^ci
=;4˜BX$("7‹\4Ÿánm?6.-EI`O'!
‚¡.e
>C8”B$*¬Ì0)q2W);N@[¯ý²¬_Z(=<81.5OFZ''Dþ¾)TI}@)I<ž
ECI #Z` þK_W+'?fmOþÙ(&N
"?'þó79E]` Éþ"?3+0 Š+"$þ( 
 2>QtY=P
PWDF†h)tg"&1`Wþ&"?A˜ê±Q$ì¯V.b‹ž!#'‰!1N˜&
PWHI†g!(*gXý%#<D
Êœ5V.b‹ž!0C¤'*
QVHž03G²//#ÕC(ɝþú'.a
@A9•<$0Dþç—6_nV=“`ÊF‹+X,	6:7:/03OEZ! 0ýó"÷º*Ÿ}>£
PWHxÖ+"T-70À…Z2bä )0	J$
!HV'fgGOHþ}M`bX*';i\2uI„„
PWKŸGÇUN "a¾]X'  †-+5V~.bþÙ''ä±þ×* O
PWKž3€7- %Ò	lj2/<f\,bþÙ$"hi$$8U2$`
PWKžc€bKž3€7- %Ò	lj2/s2/<f\,bþÙ$"å°þÙ$"hi$$8U2$`
ˆ¥,
PWKŸ!GÇUþù79E]¨þ¸gC"r+0 ±†-+5V~.bþÙ'' ä±þ( 

2G%²M9tþÓýº!
þ~ù0þ>­S…€
·
ÕÎ
	ª"
c., with final additions and corrections provided by Coen Hoffman, Elsevier (retired)
i
o


 !"#$%&'
 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^

 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopuni00A0uni00ADuni0100uni0101uni0102uni0103uni0104uni0105uni0106uni0107uni0108uni0109uni010Auni010Buni010Cuni010Duni010Euni010Funi0110uni0111uni0112uni0113uni0116uni0117uni011Cuni011Duni0120uni0121uni0122uni0124uni0125uni0126uni0127uni0128uni0129uni012Auni012Buni0130uni0132uni0133uni0134uni0135uni0138uni013Duni013Euni013Funi0140uni014Auni014Buni014Cuni014Duni0150uni0151uni015Auni015Buni015Cuni015Duni0165uni0166uni0167uni0168uni0169uni016Auni016Buni016Euni016Funi0170uni0171uni0174uni0175uni0176uni0177uni0179uni017Auni017Buni017Cuni017Funi0180uni0188uni0190uni0195uni0199uni019Auni019Buni019Euni01A0uni01A1uni01A5uni01AAuni01ABuni01ADuni01B5uni01BAuni01BBuni01BEuni01C0uni01C1uni01C2u!
 ni01C3uni01F0uni0221uni02B9uni02BAuni02BBuni02BCuni02BDuni02BEuni02BFuni02C2uni02C3uni02C4uni02C5uni02C8uni02C9uni02CAuni02CBuni02CCuni02CDuni02CEuni02CFuni02D6uni02D7uni02DFuni02ECuni02EDuni02F7uni0300uni0301uni0302uni0303uni0304uni0305uni0306uni0307uni0308uni030Auni030Buni030Cuni030Duni030Euni030Funi0311uni0312uni0313uni0314uni0315uni031Auni031Buni031Duni031Euni031Funi0320uni0327uni033Funi0359uni035Cuni0360uni0361uni0362uni037Euni0384uni0385uni0387uni0391uni0392uni0393uni0394uni0395uni0396uni0397uni0398uni0399uni039Auni039Buni039Cuni039Duni039Euni039Funi03A0uni03A1uni03A3uni03A4uni03A5uni03A6uni03A7uni03A8uni03A9uni03AAuni03ABuni03ACuni03ADuni03AEuni03AFuni03B0uni03B1uni03B2uni03B3uni03B4uni03B5uni03B6uni03B7uni03B8uni03B9uni03BAuni03BBuni03BCuni03BDuni03BEuni03BFuni03C0uni03C1uni03C2uni03C3uni03C4uni03C5uni03C6uni03C7uni03C8uni03C9uni03CAuni03CBuni03!
 CCuni03CDuni03CEuni03D0uni03D1uni03D2uni03D5uni03D6uni03D8uni
03D9uni03DAuni03DBuni03DCuni03DDuni03DEuni03DFuni03E0uni03E1uni03F0uni03F1uni03F4uni03F5uni03F6uni0401uni0402uni0403uni0404uni0405uni0406uni0407uni0408uni0409uni040Auni040Buni040Cuni040Euni040Funi0410uni0411uni0412uni0413uni0414uni0415uni0416uni0417uni0418uni0419uni041Auni041Buni041Cuni041Duni041Euni041Funi0420uni0421uni0422uni0423uni0424uni0425uni0426uni0427uni0428uni0429uni042Auni042Buni042Cuni042Duni042Euni042Funi0430uni0431uni0432uni0433uni0434uni0435uni0436uni0437uni0438uni0439uni043Auni043Buni043Cuni043Duni043Euni043Funi0440uni0441uni0442uni0443uni0444uni0445uni0446uni0447uni0448uni0449uni044Auni044Buni044Cuni044Duni044Euni044Funi0451uni0452uni0453uni0454uni0455uni0456uni0457uni0458uni0459uni045Auni045Buni045Cuni045Euni045Funi0462uni0463uni046Auni046Buni0472uni0473uni0474uni0475uni0490uni0491uni2010uni2011
figuredashuni2015uni2016uni2017uni201Buni201Ftwodotenleaderuni2031uni2032uni2033uni2034uni2035uni2036uni2037uni2038uni203Euni2040uni2043uni204Euni2052uni205F	nsuperioruni20ACuni20D0uni20D1uni20D2uni20D6uni20D7uni20DBuni20DCuni20DDuni20DEuni20E1uni20E5uni20E6uni20E7uni20E8uni20E9uni20EAuni20EBuni20EEuni20EFuni20F0uni2102uni2107uni210Auni210Buni210Cuni210Duni210Euni210Funi2110uni2111uni2112uni2113uni2115uni2116uni2118uni2119uni211Auni211Buni211Cuni211Duni211Euni2124uni2125uni2126uni2127uni2128uni2129uni212Buni212Cuni212Duni212Euni212Funi2130uni2131uni2132uni2133uni2134uni2135uni2136uni2137uni2138uni213Cuni213Duni213Euni213Funi2140uni2145uni2146uni2147uni2148uni2149onethird	twothirdsuni2155uni2156uni2157uni2158uni2159uni215Auni2190uni2191uni2192uni2193uni2194uni2195uni2196uni2197uni2198uni2199uni219Auni219Buni21A4uni21A5uni21A6uni21A7uni21AEuni21B0uni21B1uni21B!
 2uni21B3uni21B4uni21B5uni21B6uni21B7uni21B9uni21BAuni21BBuni21C4uni21CDuni21CEuni21CFuni21D0uni21D1uni21D2uni21D3uni21D4uni21D5uni21E0uni21E1uni21E2uni21E3uni21E4uni21E5uni21E6uni21E7uni21E8uni21E9uni21F5uni21F7uni21F8uni21F9uni21FAuni21FDuni21FEuni21FFuni2200uni2201uni2202uni2203uni2204uni2205uni2206uni2207uni2208uni2209uni220Auni220Buni220Cuni220Duni220Funi2210uni2211uni2213uni2214uni2215uni2216uni2217uni2218uni2219uni221Duni221Euni221Funi2220uni2221uni2222uni2223uni2224uni2225uni2226uni2227uni2228uni2229uni222Auni222Buni222Cuni222Duni222Euni222Funi2230uni2231uni2232uni2233uni2234uni2235uni2236uni2237uni2238uni223Buni223Cuni223Duni223Euni223Funi2240uni2241uni2242uni2243uni2244uni2245uni2246uni2247uni2248uni2249uni224Auni224Buni224Cuni224Duni225Duni2260uni2261uni2262uni2263uni2264uni2265uni2266uni2267uni2268uni2269uni226Auni226Buni226Cuni226Duni226Eun!
 i226Funi2270uni2271uni2272uni2273uni2274uni2275uni2276uni2277
uni2278uni2279uni227Auni227Buni227Cuni227Duni227Euni227Funi2280uni2281uni2282uni2283uni2284uni2285uni2286uni2287uni2288uni2289uni228Auni228Buni228Funi2290uni2291uni2292uni2293uni2294uni2295uni2296uni2297uni2298uni2299uni22A2uni22A3uni22A4uni22A5uni22A6uni22A7uni22A8uni22A9uni22ACuni22B2uni22B3uni22BAuni22BEuni22BFuni22C0uni22C1uni22C2uni22C3uni22C4uni22C5uni22C6uni22CEuni22CFuni22D5uni22DAuni22DBuni22DCuni22DDuni22DEuni22DFuni22E0uni22E1uni2300uni2305uni2308uni2309uni230Auni230Buni2312uni2316uni2320uni2321uni2322uni2323uni2329uni232Auni233Funi2340uni2393uni239Buni239Cuni239Duni239Euni239Funi23A0uni23A1uni23A2uni23A3uni23A4uni23A5uni23A6uni23A7uni23A8uni23A9uni23AAuni23ABuni23ACuni23ADuni23AEuni23AFuni23B0uni23B1uni23CEuni23D0uni23DCuni23DDuni23DEuni23DFuni23E0uni23E1uni23E4uni2605uni2606uni2609uni263Cuni263Duni263Euni263Funi2640uni2641uni2642uni2643uni26!
 44uni2646uni2647uni2648uni2649uni26E2uni279Buni27F5uni27F6uni27F7uni27F8uni27F9uni27FAuni27FBuni27FCuni27FDuni27FEuni2902uni2903uni2904uni2906uni2907uni2912uni2913uni2934uni2935uni2936uni2937uni2938uni2939uni293Auni293Buni293Cuni293Duni293Euni293Funi2940uni2941uni2981uni299Buni299Cuni299Duni299Euni299Funi29A0uni29A1uni29A8uni29A9uni29AAuni29ABuni29ACuni29ADuni29AEuni29AFuni29BBuni29BFuni29C0uni29C1uni29E3uni29E7uni29FAuni29FCuni29FDuni2A00uni2A01uni2A02uni2A03uni2A04uni2A05uni2A06uni2A09uni2A0Auni2A0Buni2A0Cuni2A0Duni2A0Euni2A0Funi2A20uni2A2Funi2A3Funi2A42uni2A43uni2A7Duni2A7Euni2A95uni2A96uni2A99uni2A9Auni2A9Buni2A9Cuni2AFDuni2B50uni2B51uniA727u1D49Cu1D49Eu1D49Fu1D4A2u1D4A5u1D4A6u1D4A9u1D4AAu1D4ABu1D4ACu1D4AEu1D4AFu1D4B0u1D4B1u1D4B2u1D4B3u1D4B4u1D4B5u1D4B6u1D4B7u1D4B8u1D4B9u1D4BBu1D4BDu1D4BEu1D4BFu1D4C0u1D4C1u1D4C2u1D4C3u1D4C5u1D4C6u1D4C7u1D4C8u1D!
 4C9u1D4CAu1D4CBu1D4CCu1D4CDu1D4CEu1D4CFu1D538u1D539u1D53Bu1D
53Cu1D53Du1D53Eu1D540u1D541u1D542u1D543u1D544u1D546u1D54Au1D54Bu1D54Cu1D54Du1D54Eu1D54Fu1D550





€
š,,::HNd~°Êäþ







Index: modules/damieng/graphical_editor/daxe/lib/src/attribute_dialog.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/attribute_dialog.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

class AttributeDialog {
  DaxeNode el;
  x.Element ref;
  List<x.Element> attRefs;
  HashMap<x.Element, SimpleTypeControl> controls;
  HashMap<DaxeAttr, h.TextInputElement> unknownAttributeFields;
  ActionFunction okfct;
  
  AttributeDialog(this.el, [this.okfct]) {
    ref = el.ref;
    controls = new HashMap<x.Element, SimpleTypeControl>();
    unknownAttributeFields = null;
  }
  
  void show() {
    h.DivElement div1 = new h.DivElement();
    div1.id = 'attributes_dlg';
    div1.classes.add('dlg1');
    h.DivElement div2 = new h.DivElement();
    div2.classes.add('dlg2');
    h.DivElement div3 = new h.DivElement();
    div3.classes.add('dlg3');
    h.DivElement title = new h.DivElement();
    title.classes.add('dlgtitle');
    title.text = doc.cfg.elementTitle(ref);
    div3.append(title);
    h.FormElement form = new h.FormElement();
    h.TableElement table = new h.TableElement();
    attRefs = doc.cfg.elementAttributes(ref);
    SimpleTypeControl toFocus = null;
    for (x.Element attref in attRefs) {
      h.TableRowElement tr = new h.TableRowElement();
      h.TableCellElement td = new h.TableCellElement();
      String attdoc = doc.cfg.attributeDocumentation(ref, attref);
      if (attdoc != null) {
        h.ButtonElement bHelp = new h.ButtonElement();
        bHelp.attributes['type'] = 'button';
        bHelp.classes.add('help');
        bHelp.value = '?';
        bHelp.text = '?';
        bHelp.title = attdoc;
        bHelp.onClick.listen((h.Event event) => help(attref, ref));
        td.append(bHelp);
      }
      tr.append(td);
      td = new h.TableCellElement();
      String name = doc.cfg.attributeQualifiedName(ref, attref);
      String title = doc.cfg.attributeTitle(ref, attref);
      td.appendText(title);
      if (doc.cfg.requiredAttribute(ref, attref))
        td.classes.add('required');
      else
        td.classes.add('optional');
      tr.append(td);
      td = new h.TableCellElement();
      String value = el.getAttribute(name);
      String defaultValue = doc.cfg.defaultAttributeValue(attref);
      if (value == null) {
        if (defaultValue != null)
          value = defaultValue;
        else
          value = '';
      }
      SimpleTypeControl control = new SimpleTypeControl.forAttribute(ref, attref, value);
      controls[attref] = control;
      List<String> values = doc.cfg.attributeValues(attref);
      if (values == null || values.length == 0)
        if (toFocus == null)
          toFocus = control;
      td.append(control.html());
      tr.append(td);
      table.append(tr);
    }
    for (DaxeAttr att in el.attributes) {
      bool found = false;
      for (x.Element attref in controls.keys) {
        if (att.localName == doc.cfg.attributeName(attref) &&
            att.namespaceURI == doc.cfg.attributeNamespace(attref)) {
          found = true;
          break;
        }
      }
      if (!found) {
        if (unknownAttributeFields == null)
          unknownAttributeFields = new HashMap<DaxeAttr, h.TextInputElement>();
        h.TextInputElement input = new h.TextInputElement();
        input.spellcheck = false;
        input.size = 40;
        input.value = att.value;
        input.classes.add('invalid');
        unknownAttributeFields[att] = input;
        h.TableRowElement tr = new h.TableRowElement();
        h.TableCellElement td = new h.TableCellElement();
        tr.append(td);
        td = new h.TableCellElement();
        td.appendText(att.name);
        tr.append(td);
        td = new h.TableCellElement();
        td.append(input);
        tr.append(td);
        table.append(tr);
      }
    }
    form.append(table);
    h.DivElement div_buttons = new h.DivElement();
    div_buttons.classes.add('buttons');
    h.ButtonElement bCancel = new h.ButtonElement();
    bCancel.attributes['type'] = 'button';
    bCancel.appendText(Strings.get("button.Cancel"));
    bCancel.onClick.listen((h.MouseEvent event) => cancel());
    div_buttons.append(bCancel);
    h.ButtonElement bOk = new h.ButtonElement();
    bOk.attributes['type'] = 'submit';
    bOk.appendText(Strings.get("button.OK"));
    bOk.onClick.listen((h.MouseEvent event) => ok(event));
    div_buttons.append(bOk);
    form.append(div_buttons);
    div3.append(form);
    div2.append(div3);
    div1.append(div2);
    h.document.body.append(div1);
    if (toFocus != null)
      toFocus.focus();
  }
  
  void ok(h.MouseEvent event) {
    // check the required attributes
    for (x.Element attref in controls.keys) {
      SimpleTypeControl control = controls[attref];
      String value = control.getValue();
      bool required = doc.cfg.requiredAttribute(ref, attref);
      if (value == '' && required) {
        event.preventDefault();
        h.window.alert(Strings.get('attribute.missing_required'));
        return;
      }
    }
    
    // save and close dialog
    LinkedHashMap<String, DaxeAttr> attributes = el.getAttributesMapCopy();
    for (x.Element attref in controls.keys) {
      SimpleTypeControl control = controls[attref];
      String name = doc.cfg.attributeQualifiedName(ref, attref);
      String value = control.getValue();
      String namespace = doc.cfg.attributeNamespace(attref);
      String defaultValue = doc.cfg.defaultAttributeValue(attref);
      if ((value == '' && defaultValue == null) || value == defaultValue)
        attributes.remove(name);
      else if (value != '' || defaultValue != null)
        attributes[name] = new DaxeAttr.NS(namespace, name, value);
    }
    if (unknownAttributeFields != null) {
      for (DaxeAttr att in unknownAttributeFields.keys) {
        h.TextInputElement input = unknownAttributeFields[att];
        String name = att.name;
        String value = input.value;
        String namespace = att.namespaceURI;
        if (value == '')
          attributes.remove(name);
        else
          attributes[name] = new DaxeAttr.NS(namespace, name, value);
      }
    }
    h.querySelector('div#attributes_dlg').remove();
    event.preventDefault();
    List<DaxeAttr> attList = new List.from(attributes.values);
    if (el.getHTMLNode() != null) {
      UndoableEdit edit = new UndoableEdit.changeAttributes(el, attList);
      doc.doNewEdit(edit);
    } else {
      // this is for a new element
      el.attributes = attList;
    }
    page.focusCursor();
    if (okfct != null)
      okfct();
  }
  
  void cancel() {
    h.querySelector('div#attributes_dlg').remove();
    page.focusCursor();
  }
  
  void help(x.Element attref, x.Element ref) {
    HelpDialog dlg = new HelpDialog.Attribute(attref, ref);
    dlg.show();
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/config.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/config.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;


/**
 * A Jaxe configuration file. Includes many useful methods to use XML schemas.
 */
class Config {
  static final String _typeAffichageParDefaut = "string";
  
  x.Element _cfgroot; // config file root element
  
  String schemaURL; // schema file URL
  
  String _cfgdir; // config folder URL (in which the config file must be)
  
  HashMap<String, x.Element> _elementDisplayCache; // cache for associations nom -> AFFICHAGE_ELEMENT
  HashMap<x.Element, String> _elementsToNamesCache; // cache for associations element reference -> name
  HashMap<x.Element, String> _elementsTitlesCache; // cache for associations element reference -> title
  HashMap<x.Element, Pattern> _insertCache = null; // cache for regular expressions for insertions
  HashMap<x.Element, Pattern> _validPatternCache = null;
  HashMap<x.Element, HashMap<String, List<String>>> _parametersCache = null;
  List<String> _namespaceCache = null; // namespace list
  
  InterfaceSchema _schema; // all the schema management (validity...)
  
  // nodes for the config file main elements
  x.Element _languageNode;
  x.Element _savingNode;
  x.Element _menusNode;
  x.Element _displayNode;
  x.Element _exportsNode;
  List<x.Element> _listeStrings;

  
  // CONSTRUCTORS AND INITIALIZATION
  
  /**
   * Constructor (load must be called afterwards)
   */
  Config() {
  }
  
  /**
   * Load a config file
   *
   * @param cfgFilePath  path to the config file
   */
  Future load(final String cfgFilePath) { // throws DaxeException
    Completer completer = new Completer();
    if (cfgFilePath == null) {
      _cfgroot = null;
      return(new Future.error(new DaxeException("Config.load: null path")));
    }
    
    x.DOMParser dp = new x.DOMParser();
    dp.parseFromURL(cfgFilePath).then((x.Document configdoc) {
      String resource;
      if (configdoc.documentElement.nodeName == "CONFIG_JAXE")
        resource = null;
      else
        resource = _getResource(configdoc.documentElement);
      
      _cfgdir = _getParentURL(cfgFilePath);
      
      _cfgroot = configdoc.documentElement;
      
      // AUTRE_CONFIG: ignored
      
      _buildElementDisplayCache();
      
      _elementsTitlesCache = new HashMap<x.Element, String>();
      
      final String noms = schemaName();
      if (noms == null) {
        final x.Element schema_simple = _findElement(_getLanguage(), "SCHEMA_SIMPLE");
        if (schema_simple == null) {
          completer.completeError(new DaxeException("Error: no XML schema is defined in the config file $cfgFilePath"));
          return;
        }
        _schema = new SimpleSchema(schema_simple, _titlesHash());
        schemaURL = null;
        _buildElementsToNamesCache();
        completer.complete();
        return;
      }
      
      if (_cfgdir != null)
        schemaURL = "${_cfgdir}/$noms";
        else
          schemaURL = noms;
      _schema = new DaxeWXS(_titlesHash());
      (_schema as DaxeWXS).load(schemaURL).then((_) {
        _buildElementsToNamesCache();
        completer.complete();
      }, onError: (WXSException ex) {
        completer.completeError(new DaxeException("Error reading schemaURL: $ex"));
      });
    }, onError: (x.DOMException ex) {
      completer.completeError(new DaxeException("Error reading $cfgFilePath: $ex"));
    });
    return(completer.future);
  }
  
  /**
   * Returns the URL of the parent of the given URL (file or directory),
   * or null if the parent directory cannot be found
   */
  static String _getParentURL(final String u) {
    final int index = u.lastIndexOf("/");
    if (index >= 0) {
      return(u.substring(0, index));
    }
    return(null);
  }
  
  
  // METHODS RELATED TO THE CONFIG FILE
  
  /**
   * Returns the name of the first possible root element, or null if none are defined.
   */
  String nameOfFirstRootElement() {
    final x.Element racine = _findElement(_getLanguage(), "RACINE");
    if (racine == null)
      return(null);
    return(racine.getAttribute("element"));
  }
  
  /**
   * Returns the reference to the first possible root element, or null if none are defined.
   */
  x.Element firstRootElement() {
    final String nom = nameOfFirstRootElement();
    return(_schema.elementReferenceByName(nom));
  }
  
  /**
   * Returns the list of names of the possible root elements
   */
  List<String> listOfRoots() {
    final List<String> liste = new List<String>();
    x.Element racine = _findElement(_getLanguage(), "RACINE");
    while (racine != null) {
      liste.add(racine.getAttribute("element"));
      racine = _nextElement(racine, "RACINE");
    }
    return(liste);
  }
  
  /**
   * Returns the list of references to the possible root elements
   */
  List<x.Element> rootElements() {
    // pour éviter une erreur dans le cas d'un schéma définissant un élément global et un élément local
    // sous le même nom mais avec des types différents, on est obligé d'aller d'abord chercher les références
    // des éléments racines en fonction de l'implémentation du schéma, puis de chercher dedans les éléments
    // avec les noms donnés dans la config.
    final List<x.Element> liste = new List<x.Element>();
    final List<x.Element> racinesPossibles = _schema.rootElements();
    x.Element racine = _findElement(_getLanguage(), "RACINE");
    while (racine != null) {
      final String nom = racine.getAttribute("element");
      for (final x.Element ref in racinesPossibles)
        if (nom == _schema.elementName(ref))
          liste.add(ref);
          racine = _nextElement(racine, "RACINE");
    }
    return(liste);
  }
  
  /**
   * Adds the attributes for the namespaces to the root node
   */
  void addNamespaceAttributes(final DaxeNode root) {
    final List<String> espaces = _namespaceList();
    for (final String espace in espaces) {
      if (espace != "") {
        final String prefixe = namespacePrefix(espace);
        String nomatt;
        if (prefixe != null && prefixe != "")
          nomatt = "xmlns:$prefixe";
        else
          nomatt = "xmlns";
        root.setAttributeNS("http://www.w3.org/2000/xmlns/", nomatt, espace);
      }
    }
    final String schemaLocation = getSchemaLocation();
    final String noNamespaceSchemaLocation = getNoNamespaceSchemaLocation();
    if (schemaLocation != null || noNamespaceSchemaLocation != null) {
      root.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
      if (schemaLocation != null)
        root.setAttributeNS("http://www.w3.org/2001/XMLSchema-instance",
            "xsi:schemaLocation", schemaLocation);
      if (noNamespaceSchemaLocation != null)
        root.setAttributeNS("http://www.w3.org/2001/XMLSchema-instance",
            "xsi:noNamespaceSchemaLocation", noNamespaceSchemaLocation);
    }
  }
  
  /**
   * Returns the name of the schema file as given in the config file
   * (FICHIER_SCHEMA/@nom)
   * Returns null if none is defined
   */
  String schemaName() {
    final x.Element fichierschema = _findElement(_getLanguage(), "FICHIER_SCHEMA");
    if (fichierschema == null)
      return(null);
    String nom = fichierschema.getAttribute("nom");
    if (nom == "")
      nom = null;
    return(nom);
  }
  
  /**
   * Returns the hash table by name of the element displays in the config file
   * (element AFFICHAGE_ELEMENT)
   */
  HashMap<String, x.Element> _buildElementDisplayCache() {
    _elementDisplayCache = new HashMap<String, x.Element>();
    if (_cfgroot == null)
      return(_elementDisplayCache);
    x.Element affel = _findElement(_getNodeDisplay(), "AFFICHAGE_ELEMENT");
    while (affel != null) {
      final String nom = affel.getAttribute("element");
      _elementDisplayCache[nom] = affel;
      affel = _nextElement(affel, "AFFICHAGE_ELEMENT");
    }
    return(_elementDisplayCache);
  }
  
  HashMap<x.Element, String> _getElementsToNamesCache() {
    return(_elementsToNamesCache);
  }
  
  x.Element getElementDisplay(String name) {
    return _elementDisplayCache[name];
  }
  
  /**
   * Builds the hash table of associations schema reference -> element name
   */
  void _buildElementsToNamesCache() {
    _elementsToNamesCache = new HashMap<x.Element, String>();
    if (_cfgroot == null)
      return;
    final List<x.Element> elements = _schema.allElements();
    for (final x.Element ref in elements) {
      final String nom = _schema.elementName(ref);
      if (nom != null)
        _elementsToNamesCache[ref] = nom;
    }
  }
  
  /**
   * Return the name of the resource bundle to use.
   *
   * @return the name of the resource bundle, null if not defined.
   */
  String _getResource(final x.Element root) {
    final x.Element bundle = _findElement(root, "FICHIERTITRES");
    if (bundle == null)
      return(null);
    return(bundle.getAttribute("nom"));
  }
  
  /**
   * Returns the list of export references, depending on the output (HTML or XML)
   */
  List<x.Element> exportsList(final String output) {
    if (_cfgroot == null)
      return(null);
    final List<x.Element> liste = new List<x.Element>();
    x.Element export = _findElement(_getExports(), "EXPORT");
    while (export != null) {
      if (output == export.getAttribute("sortie"))
        liste.add(export);
      export = _nextElement(export, "EXPORT");
    }
    return(liste);
  }
  
  /**
   * Returns an export name based on its reference
   */
  String exportName(final x.Element exportRef) {
    return(exportRef.getAttribute("nom"));
  }
  
  /**
   * Returns the output of an export based on its reference
   */
  String exportOutput(final x.Element exportRef) {
    return(exportRef.getAttribute("sortie"));
  }
  
  /**
   * Returns the character encoding to use for new XML documents
   */
  String getEncoding() {
    final x.Element encodage = _findElement(_getSaving(), "ENCODAGE");
    if (encodage == null)
      return(null);
    return(_dom_elementValue(encodage));
  }
  
  String getPublicId() {
    final x.Element doctype = _findElement(_getSaving(), "DOCTYPE");
    if (doctype != null)
      return doctype.getAttribute("publicId");
    
    return(null);
  }
  
  String getSystemId() {
    final x.Element doctype = _findElement(_getSaving(), "DOCTYPE");
    if (doctype != null)
      return doctype.getAttribute("systemId");
    return(null);
  }
  
  String getSchemaLocation() {
    final x.Element sl = _findElement(_getSaving(), "SCHEMALOCATION");
    if (sl != null) {
      final String schemaLocation = sl.getAttribute("schemaLocation");
      if (schemaLocation != "")
        return(schemaLocation);
    }
    return(null);
  }
  
  String getNoNamespaceSchemaLocation() {
    final x.Element sl = _findElement(_getSaving(), "SCHEMALOCATION");
    if (sl != null) {
      final String noNamespaceSchemaLocation = sl.getAttribute("noNamespaceSchemaLocation");
      if (noNamespaceSchemaLocation != "")
        return(noNamespaceSchemaLocation);
    }
    return(null);
  }
  
  /**
   * Returns a prefix to use for the given namespace, or null if none is found
   */
  String namespacePrefix(final String namespace) {
    if (namespace == "http://www.w3.org/XML/1998/namespace")
      return("xml");
    x.Element pe = _findElement(_getSaving(), "PREFIXE_ESPACE");
    while (pe != null) {
      if (namespace == pe.getAttribute("uri"))
        return(pe.getAttribute("prefixe"));
      pe = _nextElement(pe, "PREFIXE_ESPACE");
    }
    return(_schema.namespacePrefix(namespace));
  }
  
  
  // METHODS FOR THE ELEMENT INSERT MENUS
  
  /**
   * Returns a menu matching the menu definition in the config file.
   *
   * @param doc  The Daxe document
   * @param menudef  The MENU element in the config file
   */
  Menu _creationMenu(final DaxeDocument doc, final x.Element menudef) {
    final String nomMenu = menudef.getAttribute("nom");
    String titreM = menuTitle(nomMenu);
    final Menu menu = new Menu(titreM);
    String docMenu = menuDocumentation(nomMenu);
    if (docMenu != null) {
      //docMenu = "<html><body>{docMenu.replaceAll('\n', '<br>')}</body></html>";
      menu.toolTipText = docMenu;
    }
    x.Node menunode = menudef.firstChild;
    while (menunode != null) {
      MenuItem item = null;
      final String nodename = menunode.nodeName;
      String shortcut = null;
      if (menunode is x.Element) {
        final String commande = (menunode as x.Element).getAttribute("raccourci");
        if (commande != null && commande != "") {
          shortcut = commande.toUpperCase()[0];
        }
      }
      if (nodename == "MENU_INSERTION") {
        final x.Element insnoeud = menunode as x.Element;
        final String nom = insnoeud.getAttribute("nom");
        final String titre = menuTitle(nom);
        String typeNoeud = insnoeud.getAttribute("type_noeud");
        if (typeNoeud == "")
          typeNoeud = "element";
        x.Element refElement;
        if (typeNoeud == "element") {
          refElement = elementReference(nom);
          if (refElement == null)
            logError("Erreur: MENU_INSERTION: pas de référence pour '$nom' dans le schéma");
        } else
          refElement = null;
        item = new MenuItem(titre, () => doc.insertNewNode(refElement, typeNoeud), shortcut: shortcut, data: refElement);
        menu.add(item);
        String itemdoc = documentation(refElement);
        if (itemdoc != null) {
          //itemdoc = formatDoc(itemdoc);
          item.toolTipText = itemdoc;
        }
      } else if (nodename == "MENU_FONCTION") {
        final x.Element fonction = menunode as x.Element;
        final String classe = fonction.getAttribute("classe");
        final String nom = fonction.getAttribute("nom");
        final String titre = menuTitle(nom);
        item = new MenuItem(titre, () => doc.executeFunction(classe, fonction), shortcut: shortcut);
        menu.add(item);
        String itemdoc = menuDocumentation(nom);
        if (itemdoc != null) {
          //itemdoc = formatDoc(itemdoc);
          item.toolTipText = itemdoc;
        }
      } else if (nodename == "MENU") {
        item = _creationMenu(doc, menunode as x.Element);
        menu.add(item);
      } else if (nodename == "SEPARATEUR")
        menu.addSeparator();
      
      menunode = menunode.nextSibling;
    }
    return(menu);
  }
  
  /**
   * Returns a menubar to insert menus.
   *
   * @param doc  The Daxe document
   */
  MenuBar makeMenus(final DaxeDocument doc) {
    final MenuBar mbar = new MenuBar();
    
    final x.Element menus = _getMenus();
    if (menus != null) {
      x.Element menudef = _findElement(menus, "MENU");
      while (menudef != null) {
        final Menu jmenu = _creationMenu(doc, menudef);
        jmenu.parent = mbar;
        mbar.add(jmenu);
        menudef = _nextElement(menudef, "MENU");
      }
    }
    return(mbar);
  }
  
  
  // METHODS RELATED TO THE SCHEMA
  
  InterfaceSchema getSchema() {
    return(_schema);
  }
  
  /**
   * Returns the references for all the elements in the schema
   */
  List<x.Element> allElementsList() {
    final List<x.Element> liste = _schema.allElements();
    return(liste);
  }
  
  bool _elementInSchema(final x.Element elementRef) {
    return(_schema.elementInSchema(elementRef));
  }
  
  /**
   * Returns the name of the element
   */
  String elementName(final x.Element elementRef) {
    return(_elementsToNamesCache[elementRef]);
  }
  
  /**
   * Returns the reference of the first matching element in the schema,
   * based on the element and the reference of its parent
   */
  x.Element getElementRef(final x.Element el, final x.Element parentRef) {
    return(_schema.elementReference(el, parentRef));
  }
  
  /**
   * Returns the reference for the first element with the given name
   */
  x.Element elementReference(final String name) {
    final x.Element el = _schema.elementReferenceByName(localValue(name));
    return(el);
  }
  
  /**
   * Returns the references of the elements with the given name
   */
  List<x.Element> elementReferences(final String name) {
    return(_schema.elementReferencesByName(localValue(name)));
  }
  
  /**
   * Returns the namespace to use for the element,
   * or null the namespace is undefined.
   */
  String elementNamespace(final x.Element elementRef) {
    return(_schema.elementNamespace(elementRef));
  }
  
  /**
   * Returns the prefix to use for a new element with the given reference,
   * or null if no prefix should be used.
   */
  String elementPrefix(final x.Element elementRef) {
    final String espace = elementNamespace(elementRef);
    if (espace == null)
      return(null);
    return(namespacePrefix(espace));
  }
  
  /**
   * Returns the list of possible values for an element.
   * Returns null if there are an infinity of possible values.
   */
  List<String> elementValues(final x.Element elementRef) {
    final List<String> liste = _schema.elementValues(elementRef);
    return(liste);
  }
  
  /**
   * Returns true if the given value is valid for the element
   */
  bool isElementValueValid(final x.Element elementRef, final String value) {
    return(_schema.elementValueIsValid(elementRef, value));
  }
  
  /**
   * Returns the list of all namespaces in the schema
   */
  List<String> _namespaceList() {
    if (_namespaceCache != null)
      return(_namespaceCache);
    final List<String> liste = new List<String>();
    final List<String> espacesSchema = _schema.namespaceList();
    if (espacesSchema != null)
      liste.addAll(espacesSchema);
    _namespaceCache = liste;
    return(liste);
  }
  
  /**
   * Returns a number for the given namespace, starting from 0.
   * A unique number is given for each namespace.
   * Returns -1 if the namespace is not found in the config.
   */
  int namespaceNumber(final String namespace) {
    final List<String> liste = _namespaceList();
    return(liste.indexOf(namespace));
  }
  
  /**
   * Returns true if the namspace is defined in the config
   */
  bool hasNamespace(final String namespace) {
    return(_schema.hasNamespace(namespace));
  }
  
  /**
   * Returns the target namespace for the schema (targetNamespace attribute for WXS)
   */
  String targetNamespace() {
    return(_schema.getTargetNamespace());
  }
  
  /**
   * Returns the references of the elements which are not in the given namespace
   */
  List<x.Element> elementsOutsideNamespace(final String namespace) {
    return(_schema.elementsOutsideNamespace(namespace));
  }
  
  /**
   * Returns the references of the elements which are in the given namespaces
   */
  List<x.Element> elementsWithinNamespaces(final Set<String> namespaces) {
    return(_schema.elementsWithinNamespaces(namespaces));
  }
  
  /**
   * Returns true if the child is required under the parent.
   */
  bool requiredElement(final x.Element parentRef, final x.Element childRef) {
    return(_schema.requiredElement(parentRef, childRef));
  }
  
  /**
   * Returns true if there is a relation parent-child between the 2 elements
   */
  bool isSubElement(final x.Element parentRef, final x.Element childRef) {
    final List<x.Element> children = subElements(parentRef);
    if (children == null)
      return(false);
    return(children.contains(childRef));
  }
  
  /**
   * Returns the first reference in the list that is a child of the parent, or null if none is found.
   */
  x.Element findSubElement(final x.Element parentRef, final List<x.Element> refs) {
    final List<x.Element> children = subElements(parentRef);
    if (children == null)
      return(null);
    for (x.Element ref in refs)
      if (children.contains(ref))
        return(ref);
    return(null);
  }
  
  /**
   * Returns true if the given name matches a possible child for the given parent
   */
  bool isSubElementByName(final x.Element parentRef, String childName) {
    final int inds = childName.indexOf(':');
    if (inds != -1)
      childName = childName.substring(inds+1);
    final List<String> noms = subElementsNames(parentRef);
    return(noms.contains(childName));
  }
  
  /**
   * Returns the references of the given element's children
   */
  List<x.Element> subElements(final x.Element parentRef) {
    return(_schema.subElements(parentRef));
  }
  
  /**
   * Returns the names of the given element's children
   */
  List<String> subElementsNames(final x.Element parentRef) {
    final List<x.Element> listeReferences = subElements(parentRef);
    final List<String> listeNoms = new List<String>();
    for (final x.Element ref in listeReferences) {
      final String nom = _elementsToNamesCache[ref];
      if (!listeNoms.contains(nom))
        listeNoms.add(nom);
    }
    return(listeNoms);
  }
  
  /**
   * Regular expression for a given element
   * @param modevisu  True to get a regular expression to display to the user
   * @param modevalid  For strict validation instead of checking if an insert is possible
   */
  String _regularExpression(final x.Element parentRef, final bool modevisu, final bool modevalid) {
    return(_schema.regularExpression(parentRef, modevisu, modevalid));
  }
  
  /**
   * Regular expression based on the schema for a given parent element
   */
  String regularExpression(final x.Element parentRef) {
    return(_schema.regularExpression(parentRef, true, false));
  }
  
  /**
   * Returns true if the toInsert element can be inserted under the parent element
   * on the selection defined by the start and end positions
   */
  bool insertIsPossible(DaxeNode parent, final int startOffset, final int endOffset, final x.Element toInsert) {
    if (parent.nodeType == DaxeNode.DOCUMENT_NODE) {
      for (DaxeNode dn in parent.childNodes) {
        if (dn.nodeType == DaxeNode.ELEMENT_NODE)
          return(false);
      }
      return(true);
    }
    assert(parent.nodeType == DaxeNode.ELEMENT_NODE);
    if (_schema is SimpleSchema)
      return(true); // on suppose que le test de sous-élément a déjà été fait
    if (startOffset < 0) {
      logError("Config.insertionPossible: debutSelection < parent.debut");
      return(false);
    }
    if (_schema is DaxeWXS) {
      final List<x.Element> sousElements = new List<x.Element>();
      bool ajoute = false;
      for (DaxeNode dn = parent.firstChild; dn != null; dn = dn.nextSibling) {
        if (dn.nodeType == DaxeNode.ELEMENT_NODE) {
          int offset = parent.offsetOf(dn);
          if (offset < startOffset || offset >= endOffset) {
            if (!ajoute && offset >= endOffset) {
              sousElements.add(toInsert);
              ajoute = true;
            }
            sousElements.add(dn.ref);
          }
        }
      }
      if (!ajoute)
        sousElements.add(toInsert);
      final bool insertionOK = (_schema as DaxeWXS).validElement(parent.ref, sousElements, true);
      return(insertionOK);
    }
    return(false);
/*
    pb: on ne peut pas tester l'ordre des éléments dans certains cas, par exemple:
    <html>
        <head>
            <xsl:if test='truc'>
                <title>xxx</title>
            </xsl:if>
            <xsl:if test='not(truc)'>
                <title>yyy</title>
            </xsl:if>
        </head>
    </html>
    Ici on autorise deux éléments title sous head alors qu'un seul est normalement autorisé.
    Par contre on peut tester les imbrications (title est autorisé sous head).
*/
  }
  
  /**
   * Returns true if the parent element is valid, considering its attributes,
   * its first level children, its node value, and its parent if there is one.
   */
  bool elementIsValid(final DaxeNode parent) {
    if (parent is DNComment || parent is DNProcessingInstruction || parent is DNCData)
      return(true);
    
    if (parent.ref == null)
      return(false);
    
    if (!attributesAreValid(parent))
      return(false);
    
    if (parent.parent != null && parent.parent.ref != null && !isSubElement(parent.parent.ref, parent.ref))
      return(false);
    
    if (parent.firstChild == null && !isElementValueValid(parent.ref, ''))
      return(false);
    else if (parent.childNodes.length == 1 && parent.firstChild is DNText && parent.firstChild.nodeValue != null &&
        !isElementValueValid(parent.ref, parent.firstChild.nodeValue))
      return(false);
    
    if (_schema is SimpleSchema)
      return(true); // on suppose que le test de sous-balise a déjà été fait
    if (_schema is DaxeWXS) {
      final List<x.Element> sousElements = new List<x.Element>();
      bool avectexte = false;
      for (DaxeNode dn = parent.firstChild; dn != null; dn = dn.nextSibling) {
        if (dn.nodeType == DaxeNode.ELEMENT_NODE && dn.ref != null) {
          sousElements.add(dn.ref);
        } else if (dn.nodeType == DaxeNode.TEXT_NODE) {
          if (dn.nodeValue.trim() != "")
            avectexte = true;
        } else if (dn is DNCData) {
          if (dn.firstChild != null && dn.firstChild.nodeValue.trim() != '')
            avectexte = true;
        }
      }
      if (avectexte && !_schema.canContainText(parent.ref))
        return(false);
      final DaxeWXS sch = _schema as DaxeWXS;
      return(sch.validElement(parent.ref, sousElements, false));
    }
    
    final x.Element refParent = parent.ref;
    final StringBuffer cettexp = new StringBuffer();
    if (_validPatternCache == null)
      _validPatternCache = new HashMap<x.Element, Pattern>();
    
    bool avectexte = false;
    DaxeNode child = parent.firstChild;
    while (child != null) {
      if (child is DNCData) {
        if (child.firstChild != null && child.firstChild.nodeValue.trim() != '')
          avectexte = true;
      } else if (child.nodeType == DaxeNode.ELEMENT_NODE && child is! DNComment && child is! DNProcessingInstruction)  {
        cettexp.write(child.localName);
        cettexp.write(",");
      } else if (child.nodeType == DaxeNode.TEXT_NODE) {
        if (child.nodeValue.trim() != '')
          avectexte = true;
      }
      child = child.nextSibling;
    }
    if (avectexte && !_schema.canContainText(refParent))
      return(false);
    RegExp r = _validPatternCache[refParent];
    if (r == null) {
      final String expr = _regularExpression(refParent, false, true);
      if (expr == null || expr == "")
        return(true);
      try {
        r = new RegExp(r"^$expr$");
      } on Exception catch(ex) {
        logError("elementValide(JaxeElement, bool, List<String>) - Malformed Pattern: ^${expr}\$:", ex);
        return(true);
      }
      _validPatternCache[refParent] = r;
    }

    final bool matched = r.hasMatch(cettexp.toString());
    return(matched);
  }
  
  /**
   * Returns true if the element attributes are valid and if there is not missing required attribute.
   */
  bool attributesAreValid(final DaxeNode dn) {
    if (dn.nodeType != DaxeNode.ELEMENT_NODE) {
      logError("Config.attributsValides : ce n'est pas un élément: $dn");
      return(false);
    }
    // vérif des attributs qui sont dans le schéma
    final List<x.Element> lattref = elementAttributes(dn.ref);
    List<String> noms = new List<String>(lattref.length);
    List<String> espaces = new List<String>(noms.length);
    for (int i=0; i<lattref.length; i++) {
      final x.Element attref = lattref[i];
      noms[i] = attributeName(attref);
      espaces[i] = attributeNamespace(attref);
      final String valeur = dn.getAttribute(noms[i]);
      if (valeur == null || valeur == '') {
        if (requiredAttribute(dn.ref, attref))
          return(false);
      } else if (!validAttributeValue(attref, valeur))
        return(false);
    }
    // vérif s'il y a des attributs en plus qui ne sont pas dans le schéma
    final List<DaxeAttr> latt = dn.attributes;
    for (int i=0; i<latt.length; i++) {
      DaxeAttr att = latt[i];
      final String prefixe = att.prefix;
      if (prefixe == "xml" || prefixe == "xmlns")
        continue;
      final String nom = att.localName;
      if (prefixe == null && nom == "xmlns")
        continue;
      final String espace = att.namespaceURI;
      if (espace == "http://www.w3.org/2001/XMLSchema-instance")
        continue;
      bool trouve = false;
      for (int j=0; j<noms.length; j++) {
        if (noms[j] == nom && espaces[j] == espace) {
          trouve = true;
          break;
        }
      }
      if (!trouve)
        return(false);
    }
    return(true);
  }
  
  /**
   * Returns the list of possible parent elements for a given element
   */
  List<x.Element> parentElements(final x.Element elementRef) {
    return(_schema.parentElements(elementRef));
  }
  
  /**
   * Returns the list of names for possible parent elements for a given element
   */
  List<String> parentNames(final x.Element elementRef) {
    final List<x.Element> listeReferences = parentElements(elementRef);
    final List<String> listeNoms = new List<String>();
    for (final x.Element ref in listeReferences) {
      final String nom = _elementsToNamesCache[ref];
      if (!listeNoms.contains(nom))
        listeNoms.add(nom);
    }
    return(listeNoms);
  }
  
  /**
   * Returns true if the given element can contain text
   */
  bool canContainText(final x.Element elementRef) {
    if (elementRef == null)
      return(true);
    return(_schema.canContainText(elementRef));
  }
  
  /**
   * Returns the list of possible attributes for a given element.
   */
  List<x.Element> elementAttributes(final x.Element elementRef) {
    return(_schema.elementAttributes(elementRef));
  }
  
  /**
   * Returns the name of an attribute based on its reference.
   */
  String attributeName(final x.Element attributeRef) {
    return(_schema.attributeName(attributeRef));
  }
  
  /**
   * Returns the qualified name of an attribute based on its reference.
   */
  String attributeQualifiedName(final x.Element parentRef, final x.Element attributeRef) {
    String name = _schema.attributeName(attributeRef);
    String namespace = _schema.attributeNamespace(attributeRef);
    if (namespace != null) {
      String prefix = attributePrefix(parentRef, attributeRef);
      if (prefix != null)
        name = "$prefix:$name";
    }
    return(name);
  }
  
  /**
   * Returns an attribute namespace based on its reference, or null if none is defined.
   */
  String attributeNamespace(final x.Element attributeRef) {
    return(_schema.attributeNamespace(attributeRef));
  }
  
  /**
   * Returns the prefix tu use to create an attribute, given the parent element and the attribute reference,
   * or null if no prefix should be used.
   */
  String attributePrefix(final x.Element parent, final x.Element attributeRef) {
    final String espace = attributeNamespace(attributeRef);
    if (espace == null)
      return(null);
    if (espace == "http://www.w3.org/XML/1998/namespace")
      return("xml");
    if (espace == "http://www.w3.org/2000/xmlns/" && attributeName(attributeRef) != "xmlns")
      return("xmlns");
    // on essaye lookupPrefix avec le parent et avec son document
    // (cas d'un élément en cours de création, pas encore inséré dans le document)
    String prefixe = parent.lookupPrefix(espace);
    if (prefixe == null) {
      if (parent.ownerDocument.documentElement != null) // si l'élément racine existe
        prefixe = parent.ownerDocument.lookupPrefix(espace);
      else
        prefixe = namespacePrefix(espace); // on suppose que la racine sera créée avec ajouterAttributsEspaces
    }
    return(prefixe);
  }
  
  /**
   * Returns an attribute namespace based on its full name (including the prefix).
   */
  String attributeNamespaceByName(final String name) {
    return(_schema.attributeNamespaceByName(name));
  }
  
  /**
   * Returns true if the attribute is required for the parent element.
   */
  bool requiredAttribute(final x.Element parentRef, final x.Element attributeRef) {
    return(_schema.attributeIsRequired(parentRef, attributeRef));
  }
  
  /**
   * Returns the list of possible values for an attribute.
   * Returns null if there are an infinity of possible values.
   */
  List<String> attributeValues(final x.Element attributeRef) {
    final List<String> liste = _schema.attributeValues(attributeRef);
    return(liste);
  }
  
  /**
   * Returns an attribute's default value based on its reference.
   */
  String defaultAttributeValue(final x.Element attributeRef) {
    return(_schema.defaultAttributeValue(attributeRef));
  }
  
  /**
   * Returns true if the given String is a valid value for the attribute.
   */
  bool validAttributeValue(final x.Element attributeRef, final String value) {
    return(_schema.attributeIsValid(attributeRef, value));
  }
  
  /**
   * Returns the local part of an element's name (by removing the prefix).
   */
  static String localValue(final String s) {
    if (s == null)
      return(null);
    final int ind = s.indexOf(':');
    if (ind == -1)
      return(s);
    return(s.substring(ind + 1));
  }
  
  
  // METHODS FOR DISPLAY TYPES
  
  /**
   * Returns a node display type based on the element reference, the node name and the DOM node type.
   */
  String nodeDisplayType(final x.Element elementRef, final String name, final int nodeType) {
    if (nodeType == x.Node.ELEMENT_NODE) {
      final x.Element affel = getElementDisplay(localValue(name));
      if (affel == null)
        return(_typeAffichageParDefaut);
      return(affel.getAttribute("type"));
    } else if (nodeType == x.Node.PROCESSING_INSTRUCTION_NODE) {
      x.Element elplug = _findElement(_getNodeDisplay(), "PLUGIN_INSTRUCTION");
      while (elplug != null) {
        if (name != null && name == elplug.getAttribute("cible"))
          return("plugin");
        elplug = _nextElement(elplug, "PLUGIN_INSTRUCTION");
      }
      return("instruction");
    } else if (nodeType == x.Node.COMMENT_NODE) {
      final x.Element elplug = _findElement(_getNodeDisplay(), "PLUGIN_COMMENTAIRE");
      if (elplug != null)
        return("plugin");
      return("commentaire");
    } else if (nodeType == x.Node.CDATA_SECTION_NODE) {
      final x.Element elplug = _findElement(_getNodeDisplay(), "PLUGIN_CDATA");
      if (elplug != null)
        return("plugin");
      return("cdata");
    } else if (nodeType == x.Node.TEXT_NODE) {
      return("texte");
    }
    return(null);
  }

  /**
   * Returns an element display type based on its reference.
   */
  String elementDisplayType(final x.Element elementRef) {
    final x.Element affel = getElementDisplay(elementName(elementRef));
    if (affel == null)
      return(_typeAffichageParDefaut);
    return(affel.getAttribute("type"));
  }
  
  /**
   * Returns the reference of the first element with the given display type in the config file.
   */
  x.Element firstElementWithType(final String displayType) {
    if (_cfgroot == null)
      return(null);
    x.Element affel = _findElement(_getNodeDisplay(), "AFFICHAGE_ELEMENT");
    while (affel != null) {
      if (displayType == affel.getAttribute("type"))
        return(elementReference(affel.getAttribute("element")));
      affel = _nextElement(affel, "AFFICHAGE_ELEMENT");
    }
    return(null);
  }
  
  /**
   * Returns the references of the elements with the given display type in the config file.
   */
  List<x.Element> elementsWithType(final String displayType) {
    if (_cfgroot == null)
      return(null);
    List<x.Element> list = new List<x.Element>();
    x.Element affel = _findElement(_getNodeDisplay(), "AFFICHAGE_ELEMENT");
    while (affel != null) {
      if (displayType == affel.getAttribute("type"))
        list.addAll(elementReferences(affel.getAttribute("element")));
      affel = _nextElement(affel, "AFFICHAGE_ELEMENT");
    }
    return(list);
  }
  
  /**
   * Returns the value of an element display parameter.
   * @param elementRef element reference
   * @param parameterName parameter name
   * @param defaultValue default value, used if the parameter is not found
   */
  String elementParameterValue(final x.Element elementRef, final String parameterName, final String defaultValue) {
    return nodeParameterValue(elementRef, "element", null, parameterName, defaultValue);
  }

  /**
   * Returns the value of a node display parameter.
   * The node type can be used to find display parameters for comments or PIs.
   */
  String nodeParameterValue(final x.Element elementRef, final String nodeType,
                            final String name, final String parameterName, final String defaultValue) {
    final HashMap<String, List<String>> table = getNodeParameters(elementRef, nodeType, name);
    final List<String> lval = table[parameterName];
    String valeur;
    if (lval != null && lval.length > 0)
      valeur = lval[0];
    else
      valeur = defaultValue;
    return valeur;
  }

  /**
   * Returns a function parameter value.
   * @param fctdef Element for the function menu in the config file
   * @param parameterName parameter name
   * @param defaultValue default value, used if the parameter is not found
   */
  String functionParameterValue(final x.Element fctdef, final String parameterName, final String defaultValue) {
    x.Element parel = _findElement(fctdef, "PARAMETRE");
    while (parel != null) {
      final String nom = parel.getAttribute("nom");
      if (nom == parameterName)
        return(parel.getAttribute("valeur"));
      parel = _nextElement(parel, "PARAMETRE");
    }
    return(defaultValue);
  }

  HashMap<String, List<String>> _buildParameterCache(final x.Element base) {
    final HashMap<String, List<String>> hashparams = new HashMap<String, List<String>>();
    x.Element parel = _findElement(base, "PARAMETRE");
    while (parel != null) {
      final String nom = parel.getAttribute("nom");
      final String valeur = parel.getAttribute("valeur");
      List<String> lval = hashparams[nom];
      if (lval == null) {
        lval = new List<String>();
        lval.add(valeur);
        hashparams[nom] = lval;
      } else
        lval.add(valeur);
      parel = _nextElement(parel, "PARAMETRE");
    }
    _parametersCache[base] = hashparams;
    return(hashparams);
  }
  
  /**
   * Returns the table of an element display parameters.
   */
  HashMap<String, List<String>> getElementParameters(final x.Element elementRef) {
    return(getNodeParameters(elementRef, "element", null));
  }
  
  /**
   * Returns the table of a node display parameters.
   * The name can be null if nodeType is "element" and elementRef is not null.
   */
  HashMap<String, List<String>> getNodeParameters(final x.Element elementRef, final String nodeType, final String name) {
    x.Element base;
    if (nodeType == "element")
      base = getElementDisplay(elementName(elementRef));
    else if (nodeType == "instruction") {
      base = null;
      x.Element elplug = _findElement(_getNodeDisplay(), "PLUGIN_INSTRUCTION");
      while (elplug != null) {
        if (name != null && name == elplug.getAttribute("cible")) {
          base = elplug;
          break;
        }
        elplug = _nextElement(elplug, "PLUGIN_INSTRUCTION");
      }
    } else if (nodeType == "commentaire") {
      final x.Element elplug = _findElement(_getNodeDisplay(), "PLUGIN_COMMENTAIRE");
      if (elplug == null) {
        base = null;
      } else {
        base = elplug;
      }
    } else
      base = null;
    if (base == null)
      return(new HashMap<String, List<String>>());
    if (_parametersCache == null)
      _parametersCache = new HashMap<x.Element, HashMap<String, List<String>>>();
    HashMap<String, List<String>> hashparams = _parametersCache[base];
    if (hashparams == null)
      hashparams = _buildParameterCache(base);
    return(hashparams);
  }
  
  /**
   * Returns the list of suggested values for a given element.
   * Returns null if there is no suggestion.
   */
  List<String> elementSuggestedValues(final x.Element elementRef) {
    final Set<String> set = new LinkedHashSet<String>();
    List<String> schemaSuggestions = _schema.suggestedElementValues(elementRef);
    if (schemaSuggestions != null)
      set.addAll(_schema.suggestedElementValues(elementRef));
    final x.Element affel = getElementDisplay(elementName(elementRef));
    if (affel != null) {
      x.Element vs = _findElement(affel, "VALEUR_SUGGEREE");
      while (vs != null) {
        final String v = _dom_elementValue(vs);
        if (v != null)
          set.add(v);
        vs = _nextElement(vs, "VALEUR_SUGGEREE");
      }
    }
    if (set.length == 0)
      return(null);
    else
      return(set.toList());
  }
  
  /**
   * Returns the list of suggested values for an attribute,
   * based on the parent element reference and the attribute reference.
   * Returns null if there is no suggestion.
   */
  List<String> attributeSuggestedValues(final x.Element parentRef, final x.Element attributeRef) {
    final Set<String> set = new LinkedHashSet<String>();
    List<String> schemaSuggestions = _schema.suggestedAttributeValues(attributeRef);
    if (schemaSuggestions != null)
      set.addAll(schemaSuggestions);
    final x.Element affel = getElementDisplay(elementName(parentRef));
    if (affel != null) {
      final String nomAtt = attributeName(attributeRef);
      x.Element aa = _findElement(affel, "AFFICHAGE_ATTRIBUT");
      while (aa != null) {
        if (aa.getAttribute("attribut") == nomAtt) {
          x.Element vs = _findElement(aa, "VALEUR_SUGGEREE");
          while (vs != null) {
            final String v = _dom_elementValue(vs);
            if (v != null)
              set.add(v);
            vs = _nextElement(vs, "VALEUR_SUGGEREE");
          }
        }
        aa = _nextElement(aa, "AFFICHAGE_ATTRIBUT");
      }
    }
    if (set.length == 0)
      return(null);
    else
      return(set.toList());
  }
  
  
  // METHODS FOR THE STRINGS
  
  /**
   * Returns a list of the STRINGS elements in the config file,
   * ordered by preference based on the user language and country.
   */
  List<x.Element> _stringsElements() {
    final Locale defaut = new Locale();
    final List<x.Element> liste = new List<x.Element>();
    
    final List<x.Element> lstrings = _getStrings();
    for (final x.Element strings in lstrings) {
      final String langue = strings.getAttribute("langue");
      if (langue != "") {
        Locale strloc;
        if (strings.getAttribute("pays") == "")
          strloc = new Locale.l(langue);
        else
          strloc = new Locale.lc(langue, strings.getAttribute("pays"));
        if (defaut == strloc && !liste.contains(strings))
          liste.add(strings);
      }
    }
    for (final x.Element strings in lstrings) {
      final String langue = strings.getAttribute("langue");
      if (langue != "") {
        final Locale test = new Locale.lc(defaut.language, defaut.country);
        Locale strloc;
        if (strings.getAttribute("pays") == "")
          strloc = new Locale.l(langue);
        else
          strloc = new Locale.lc(langue, strings.getAttribute("pays"));
        if (test == strloc && !liste.contains(strings))
          liste.add(strings);
      }
    }
    for (final x.Element strings in lstrings) {
      final String langue = strings.getAttribute("langue");
      if (langue != "") {
        final Locale test = new Locale.l(defaut.language);
        if (test == new Locale.l(langue) && !liste.contains(strings))
          liste.add(strings);
      }
    }
    for (final x.Element strings in lstrings) {
      if (!liste.contains(strings))
        liste.add(strings);
    }
    return(liste);
  }
  
  /**
   * Returns the config description (element DESCRIPTION_CONFIG).
   */
  String description() {
    final List<x.Element> lstrings = _stringsElements();
    for (final x.Element strings in lstrings) {
      final x.Element descel = _findElement(strings, "DESCRIPTION_CONFIG");
      if (descel == null || descel.firstChild == null)
        break;
      String desc = _dom_elementValue(descel);
      return(desc);
    }
    return(null);
  }
  
  /**
   * Returns a menu title based on its name
   */
  String menuTitle(final String name) {
    final List<x.Element> lstrings = _stringsElements();
    for (final x.Element strings in lstrings) {
      x.Element sm = _findElementDeep(strings, "STRINGS_MENU");
      while (sm != null) {
        if (name == sm.getAttribute("menu")) {
          final x.Element eltitre = _findElement(sm, "TITRE");
          if (eltitre != null && eltitre.firstChild != null) {
            return(_dom_elementValue(eltitre));
          }
          break;
        }
        sm = _nextElementDeep(strings, sm, "STRINGS_MENU");
      }
    }
    final x.Element refel = elementReference(name);
    if (refel != null)
      return(elementTitle(refel));
    return(name);
  }
  
  /**
   * Returns a menu documentation based on its name.
   */
  String menuDocumentation(final String name) {
    final List<x.Element> lstrings = _stringsElements();
    for (final x.Element strings in lstrings) {
      x.Element sm = _findElementDeep(strings, "STRINGS_MENU");
      while (sm != null) {
        if (name == sm.getAttribute("menu")) {
          final x.Element eldoc = _findElement(sm, "DOCUMENTATION");
          if (eldoc != null && eldoc.firstChild != null) {
            return(_dom_elementValue(eldoc));
          }
          break;
        }
        sm = _nextElementDeep(strings, sm, "STRINGS_MENU");
      }
    }
    return(null);
  }
  
  HashMap<String, String> _titlesHash() {
    HashMap<String, String> h = new HashMap<String, String>();
    final List<x.Element> lstrings = _stringsElements();
    for (final x.Element strings in lstrings) {
      x.Element sel = _findElement(strings, "STRINGS_ELEMENT");
      while (sel != null) {
        String nom = sel.getAttribute("element");
        if (h[nom] == null) {
          String titre = nom;
          final x.Element eltitre = _findElement(sel, "TITRE");
          if (eltitre != null && eltitre.firstChild != null)
            titre = _dom_elementValue(eltitre);
          h[nom] = titre;
        }
        sel = _nextElement(sel, "STRINGS_ELEMENT");
      }
    }
    return(h);
  }
  
  /**
   * Returns an element title based on its reference.
   */
  String elementTitle(final x.Element elementRef) {
    String titre = null;
    titre = _elementsTitlesCache[elementRef];
    if (titre != null)
      return(titre);
    final String nom = elementName(elementRef);
    if (nom == null) {
      logError("Config.elementTitle : no name for $elementRef");
      return(null);
    }
    final List<x.Element> lstrings = _stringsElements();
    for (final x.Element strings in lstrings) {
      if (titre == null) {
        x.Element sel = _findElement(strings, "STRINGS_ELEMENT");
        while (sel != null) {
          if (sel.getAttribute("element") == nom) {
            final x.Element eltitre = _findElement(sel, "TITRE");
            if (eltitre != null && eltitre.firstChild != null) {
              titre = _dom_elementValue(eltitre);
              break;
            }
            break;
          }
          sel = _nextElement(sel, "STRINGS_ELEMENT");
        }
      }
    }
    if (titre == null || titre == "")
      titre = nom;
    _elementsTitlesCache[elementRef] = titre;
    return(titre);
  }
  
  /**
   * Returns an element documentation
   */
  String documentation(final x.Element elementRef) {
    if (elementRef == null)
      return(null);
    final String nom = elementName(elementRef);
    final List<x.Element> lstrings = _stringsElements();
    for (final x.Element strings in lstrings) {
      x.Element sel = _findElement(strings, "STRINGS_ELEMENT");
      while (sel != null) {
        if (nom == sel.getAttribute("element")) {
          final x.Element eldoc = _findElement(sel, "DOCUMENTATION");
          if (eldoc != null && eldoc.firstChild != null)
            return(_dom_elementValue(eldoc));
          break;
        }
        sel = _nextElement(sel, "STRINGS_ELEMENT");
      }
    }
    return(_schema.elementDocumentation(elementRef));
  }
  
  /**
   * Formats the documentation in HTML.
   */
  static String formatDoc(final String documentation) {
    String doc = documentation;
    doc = doc.replaceAll("&", "&");
    doc = doc.replaceAll("<", "<");
    doc = doc.replaceAll(">", ">");
    /*
    if (doc.length > 100) {
      int p = 0;
      for (int i=0; i<doc.length; i++) {
        if (i-p > 90 && doc[i] == ' ') {
          doc = "${doc.substring(0, i)}\n${doc.substring(i+1)}";
          p = i;
        } else if (doc[i] == '\n')
          p = i;
      }
    }
    */
    doc = doc.replaceAll("\n", "<br>");
    return(doc);
  }
  
  /**
   * Returns the title for an element value, based on the element reference and the value.
   */
  String elementValueTitle(final x.Element elementRef, final String value) {
    final String nom = elementName(elementRef);
    final List<x.Element> lstrings = _stringsElements();
    final String langueSyst = (new Locale()).language;
    for (final x.Element strings in lstrings) {
      x.Element sel = _findElement(strings, "STRINGS_ELEMENT");
      while (sel != null) {
        if (sel.getAttribute("element") == nom) {
          x.Element eltitrev = _findElement(sel, "TITRE_VALEUR");
          while (eltitrev != null) {
            if (eltitrev.getAttribute("valeur") == value &&
                eltitrev.firstChild != null)
              return(_dom_elementValue(eltitrev));
            eltitrev = _nextElement(eltitrev, "TITRE_VALEUR");
          }
          break;
        }
        sel = _nextElement(sel, "STRINGS_ELEMENT");
      }
      // la langue est trouvée mais il n'y a pas de TITRE_VALEUR correspondant
      // -> on renvoie la vraie valeur plutôt que de chercher un titre
      // dans d'autres langues.
      final String langue = strings.getAttribute("langue");
      if (langue == langueSyst)
        return(value);
    }
    return(value);
  }
  
  /**
   * Returns an attribute title based on the parent element reference and the attribute reference.
   */
  String attributeTitle(final x.Element parentRef, final x.Element attributeRef) {
    final String nomEl = elementName(parentRef);
    final String nomAtt = attributeName(attributeRef);
    final List<x.Element> lstrings = _stringsElements();
    for (final x.Element strings in lstrings) {
      x.Element sel = _findElement(strings, "STRINGS_ELEMENT");
      while (sel != null) {
        if (sel.getAttribute("element") == nomEl) {
          x.Element sat = _findElement(sel, "STRINGS_ATTRIBUT");
          while (sat != null) {
            if (sat.getAttribute("attribut") == nomAtt) {
              final x.Element eltitre = _findElement(sat, "TITRE");
              if (eltitre != null && eltitre.firstChild != null)
                return(_dom_elementValue(eltitre));
              break;
            }
            sat = _nextElement(sat, "STRINGS_ATTRIBUT");
          }
        }
        sel = _nextElement(sel, "STRINGS_ELEMENT");
      }
    }
    final String prefixe = attributePrefix(parentRef, attributeRef);
    if (prefixe != null)
      return("$prefixe:$nomAtt");
    return(nomAtt);
  }
  
  /**
   * Returns the title for an attribute value, based on the parent element reference,
   * the attribute reference and the value.
   */
  String attributeValueTitle(final x.Element parentRef, final x.Element attributeRef, final String value) {
    final String nomEl = elementName(parentRef);
    final String nomAtt = attributeName(attributeRef);
    final List<x.Element> lstrings = _stringsElements();
    final String langueSyst = (new Locale()).language;
    for (final x.Element strings in lstrings) {
      x.Element sel = _findElement(strings, "STRINGS_ELEMENT");
      while (sel != null) {
        if (sel.getAttribute("element") == nomEl) {
          x.Element sat = _findElement(sel, "STRINGS_ATTRIBUT");
          while (sat != null) {
            if (sat.getAttribute("attribut") == nomAtt) {
              x.Element eltitrev = _findElement(sat, "TITRE_VALEUR");
              while (eltitrev != null) {
                if (eltitrev.getAttribute("valeur") == value &&
                    eltitrev.firstChild != null)
                  return(_dom_elementValue(eltitrev));
                eltitrev = _nextElement(eltitrev, "TITRE_VALEUR");
              }
              break;
            }
            sat = _nextElement(sat, "STRINGS_ATTRIBUT");
          }
        }
        sel = _nextElement(sel, "STRINGS_ELEMENT");
      }
      // la langue est trouvée mais il n'y a pas de TITRE_VALEUR correspondant
      // -> on renvoie la vraie valeur d'attribut plutôt que de chercher un titre
      // dans d'autres langues.
      final String langue = strings.getAttribute("langue");
      if (langue == langueSyst)
        return(value);
    }
    return(value);
  }
  
  /**
   * Returns an attribute's documentation based on the parent element reference and
   * the attribute reference.
   */
  String attributeDocumentation(final x.Element parentRef, final x.Element attributeRef) {
    final String nomEl = elementName(parentRef);
    final String nomAtt = attributeName(attributeRef);
    final List<x.Element> lstrings = _stringsElements();
    for (final x.Element strings in lstrings) {
      x.Element sel = _findElement(strings, "STRINGS_ELEMENT");
      while (sel != null) {
        if (sel.getAttribute("element") == nomEl) {
          x.Element sat = _findElement(sel, "STRINGS_ATTRIBUT");
          while (sat != null) {
            if (sat.getAttribute("attribut") == nomAtt) {
              final x.Element eldoc = _findElement(sat, "DOCUMENTATION");
              if (eldoc != null &&eldoc.firstChild != null)
                return(_dom_elementValue(eldoc));
              break;
            }
            sat = _nextElement(sat, "STRINGS_ATTRIBUT");
          }
        }
        sel = _nextElement(sel, "STRINGS_ELEMENT");
      }
    }
    return(_schema.attributeDocumentation(attributeRef));
  }
  
  /**
   * Returns an export's title based on its reference.
   */
  String exportTitle(final x.Element exportRef) {
    final String nom = exportName(exportRef);
    final List<x.Element> lstrings = _stringsElements();
    for (final x.Element strings in lstrings) {
      x.Element export = _findElement(strings, "STRINGS_EXPORT");
      while (export != null) {
        if (nom == export.getAttribute("export")) {
          final x.Element eltitre = _findElement(export, "TITRE");
          if (eltitre != null && eltitre.firstChild != null)
            return(_dom_elementValue(eltitre));
          break;
        }
        export = _nextElement(export, "STRINGS_EXPORT");
      }
    }
    return(nom);
  }
  
  /**
   * Returns an export's documentation based on its reference.
   */
  String exportDocumentation(final x.Element exportRef) {
    final String nom = exportName(exportRef);
    final List<x.Element> lstrings = _stringsElements();
    for (final x.Element strings in lstrings) {
      x.Element export = _findElement(strings, "STRINGS_EXPORT");
      while (export != null) {
        if (nom == export.getAttribute("export")) {
          final x.Element eldoc = _findElement(export, "DOCUMENTATION");
          if (eldoc != null && eldoc.firstChild != null)
            return(_dom_elementValue(eldoc));
          break;
        }
        export = _nextElement(export, "STRINGS_EXPORT");
      }
    }
    return(null);
  }
  
  
  // TOOLS
  
  /**
   * Returns the value of the first child node, removing leading and trailing whites.
   * Returns null if there is not child node.
   */
  static String _dom_elementValue(final x.Node el) {
    final x.Node fc = el.firstChild;
    if (fc == null)
      return(null);
    final String v = fc.nodeValue;
    if (v == null)
      return(null);
    return(v.trim());
  }
  
  x.Element _getLanguage() {
    if (_languageNode == null) {
      _languageNode = _findElement(_cfgroot, "LANGAGE");
    }
    return _languageNode;
  }

  x.Element _getSaving() {
    if (_savingNode == null) {
      _savingNode = _findElement(_cfgroot, "ENREGISTREMENT");
      if (_savingNode == null) {
        _savingNode = _cfgroot.ownerDocument.createElement("ENREGISTREMENT");
      }
    }
    return _savingNode;
  }

  x.Element _getMenus() {
    if (_menusNode == null) {
      _menusNode = _findElement(_cfgroot, "MENUS");
      if (_menusNode == null) {
        _menusNode = _cfgroot.ownerDocument.createElement("MENUS");
      }
    }
    return _menusNode;
  }

  x.Element _getNodeDisplay() {
    if (_displayNode == null) {
      _displayNode = _findElement(_cfgroot, "AFFICHAGE_NOEUDS");
      if (_displayNode == null) {
        _displayNode = _cfgroot.ownerDocument.createElement("AFFICHAGE_NOEUDS");
      }
    }
    return _displayNode;
  }

  x.Element _getExports() {
    if (_exportsNode == null) {
      _exportsNode = _findElement(_cfgroot, "EXPORTS");
      if (_exportsNode == null) {
        _exportsNode = _cfgroot.ownerDocument.createElement("EXPORTS");
      }
    }
    return _exportsNode;
  }

  List<x.Element> _getStrings() {
    if (_listeStrings == null) {
      _listeStrings = new List<x.Element>();
      x.Node child = _cfgroot.firstChild;
      while (child != null) {
        if (child.nodeType == x.Node.ELEMENT_NODE && child.nodeName == "STRINGS") {
          _listeStrings.add(child as x.Element);
        }
        child = child.nextSibling;
      }
    }
    return _listeStrings;
  }

  
  static x.Element _findElement(final x.Node n, final String name) {
    final x.Node child = n.firstChild;
    return _nextNode(child, name);
  }
  
  static x.Element _nextElement(final x.Node n, final String name) {
    final x.Node child = n.nextSibling;
    return _nextNode(child, name);
  }

  static x.Element _nextNode(x.Node child, final String name) {
    if (name == null)
      return null;
    while (child != null) {
      if (child.nodeType == x.Node.ELEMENT_NODE && name == child.nodeName) {
        return child as x.Element;
      }
      child = child.nextSibling;
    }
    return null;
  }

  static x.Element _findElementDeep(final x.Node n, final String name) {
    return _nextElementDeep(n, n, name);
  }
  
  static x.Element _nextElementDeep(final x.Node parent, final x.Node n, final String name) {
    x.Node current = n;
    x.Node next;
    while (current != null) {
      if (current.hasChildNodes()) {
        current = (current.firstChild);
      } else if (current != parent && null != (next = current.nextSibling)) {
        current = next;
      } else {
        next = null;
        while (current != parent) {

          next = current.nextSibling;
          if (next != null)
            break;
          current = current.parentNode;
        }
        current = next;
      }
      if (current != parent && current != null && current.nodeType == x.Node.ELEMENT_NODE
          && current.nodeName == name) {
        return current as x.Element;
      }
    }
    return null;
  }
  
  static void logError(String message, [Exception ex]) {
    if (ex != null)
      print("Config: $message: $ex");
    else
      print("Config: $message");
  }
}


Index: modules/damieng/graphical_editor/daxe/lib/src/css_map.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/css_map.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

class CSSMap extends MapBase<String, String> {
  
  LinkedHashMap<String, String> map;
  
  CSSMap(String css) {
    map = new LinkedHashMap<String, String>();
    if (css != null) {
      for (String cssEntry in css.split(';')) {
        List<String> nameValue = cssEntry.split(':');
        if (nameValue.length == 2) {
          String name = nameValue[0].trim().toLowerCase();
          String value = nameValue[1].trim().toLowerCase();
          if (name != '' && value != '')
            map[name] = value;
        }
      }
    }
  }
  
  String operator [](String key) => map[key];
  
  void operator []=(String key, String value) {
    map[key] = value;
  }
  
  String remove(String key) {
    return(map.remove(key));
  }
  
  Iterable<String> get keys {
    return(map.keys);
  }
  
  void clear() {
    map.clear;
  }
  
  String toString() {
    List<String> cssArray = new List<String>();
    map.forEach((String key, String value) => cssArray.add(key + ': ' + value));
    return(cssArray.join('; '));
  }
  
  bool equivalent(CSSMap cssMap) {
    List<String> keys1 = keys;
    List<String> keys2 = cssMap.map.keys;
    return(keys1.every((String key1) => map[key1] == cssMap.map[key1]) &&
        keys2.every((String key2) => map[key2] == cssMap.map[key2]));
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/cursor.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/cursor.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

/**
 * Cursor and related operations (such as keyboard input)
 */
class Cursor {
  
  h.TextAreaElement ta;
  h.SpanElement caret;
  Position selectionStart, selectionEnd;
  List<h.SpanElement> spansSelection = new List<h.SpanElement>();
  List<DaxeNode> selectedNodes = new List<DaxeNode>();
  bool visible;
  static const Duration delay = const Duration(milliseconds: 700);
  Timer timer;
  HashMap<int, ActionFunction> shortcuts;
  
  Cursor() {
    ta = h.querySelector("#tacursor");
    caret = h.querySelector("#caret");
    visible = true;
    shortcuts = new HashMap<int, ActionFunction>();
    // FIXME: IE is always intercepting Ctrl-P
    ta.onKeyUp.listen((h.KeyboardEvent event) => keyUp(event));
    ta.onKeyDown.listen((h.KeyboardEvent event) => keyDown(event));
    ta.onBlur.listen((h.Event event) => blur(event));
    newTimer();
  }
  
  void setShortcuts(HashMap<String, ActionFunction> stringShortcuts) {
    HashMap<String, int> mappings = new HashMap<String, int>();
    mappings['A'] = h.KeyCode.A;
    mappings['B'] = h.KeyCode.B;
    mappings['C'] = h.KeyCode.C;
    mappings['D'] = h.KeyCode.D;
    mappings['E'] = h.KeyCode.E;
    mappings['F'] = h.KeyCode.F;
    mappings['G'] = h.KeyCode.G;
    mappings['H'] = h.KeyCode.H;
    mappings['I'] = h.KeyCode.I;
    mappings['J'] = h.KeyCode.J;
    mappings['K'] = h.KeyCode.K;
    mappings['L'] = h.KeyCode.L;
    mappings['M'] = h.KeyCode.M;
    mappings['N'] = h.KeyCode.N;
    mappings['O'] = h.KeyCode.O;
    mappings['P'] = h.KeyCode.P;
    mappings['Q'] = h.KeyCode.Q;
    mappings['R'] = h.KeyCode.R;
    mappings['S'] = h.KeyCode.S;
    mappings['T'] = h.KeyCode.T;
    mappings['U'] = h.KeyCode.U;
    mappings['V'] = h.KeyCode.V;
    mappings['W'] = h.KeyCode.W;
    mappings['X'] = h.KeyCode.X;
    mappings['Y'] = h.KeyCode.Y;
    mappings['Z'] = h.KeyCode.Z;
    for (String key in stringShortcuts.keys) {
      String up = key.toUpperCase();
      if (mappings[up] != null)
        shortcuts[mappings[up]] = stringShortcuts[key];
    }
  }
  
  static Position findPosition(h.MouseEvent event) {
    Position pos1 = doc.findPosition(event.client.x, event.client.y);
    if (pos1 == null)
      return(null);
    pos1.moveInsideTextNodeIfPossible();
    assert(pos1.dn != null);
    /*
     * we can't use window.getSelection in a MouseDown event,
     * we need another way to get the position inside text, see DaxeNode.findPosition
    if (pos1.daxeNode.nodeType == DaxeNode.TEXT_NODE) {
      h.DomSelection selection = h.window.getSelection();
      if (selection.rangeCount != 0) {
        h.Range r = selection.getRangeAt(0);
        if (r.startContainer == r.endContainer && r.startOffset == r.endOffset &&
            r.startContainer.parent.classes.contains('dn')) {
          // with "white-space: pre-wrap", bad position with a click to the right
          // of a newline caused by wrap
          Position pos2 = new Position.fromHTML(r.startContainer, r.startOffset);
          if (pos2.daxeNode == pos1.daxeNode)
            return(pos2);
        }
      }
    }
    */
    return(pos1);
  }
  
  void keyDown(h.KeyboardEvent event) {
    if (selectionStart == null)
      return;
    bool ctrl = event.ctrlKey || event.metaKey;
    bool shift = event.shiftKey;
    int keyCode = event.keyCode;
    if (ctrl && keyCode == h.KeyCode.X) {
      ta.value = copy();
      ta.select();
    } else if (ctrl && keyCode == h.KeyCode.C) {
      ta.value = copy();
      ta.select();
    } else if (keyCode == h.KeyCode.PAGE_DOWN) {
      pageDown();
    } else if (keyCode == h.KeyCode.PAGE_UP) {
      pageUp();
    } else if (keyCode == h.KeyCode.END) {
      lineEnd();
    } else if (keyCode == h.KeyCode.HOME) {
      lineStart();
    } else if (keyCode == h.KeyCode.LEFT) {
      if (shift)
        shiftLeft();
      else
        left();
    } else if (keyCode == h.KeyCode.UP) {
      up();
    } else if (keyCode == h.KeyCode.RIGHT) {
      if (shift)
        shiftRight();
      else
        right();
    } else if (keyCode == h.KeyCode.DOWN) {
      down();
    } else if (keyCode == h.KeyCode.BACKSPACE) {
      backspace();
    } else if (keyCode == h.KeyCode.DELETE) {
      suppr();
    } else if (ctrl && shortcuts[keyCode] != null) {
      event.preventDefault();
      return;
    } else if (ta.value != '') {
      // note: the first char will only be in ta.value in keyUp, this part
      // is only for long-pressed keys
      String v = ta.value;
      ta.value = '';
      doc.insertNewString(v, shift);
    } else {
      return;
    }
    newTimer();
  }
  
  void keyUp(h.KeyboardEvent event) {
    bool ctrl = event.ctrlKey || event.metaKey; // does metaKey work on keyUp ?
    bool shift = event.shiftKey;
    int keyCode = event.keyCode;
    if (selectionStart == null)
      return;
    if (ctrl && !shift && keyCode == h.KeyCode.Z) { // Ctrl Z
      doc.undo();
      ta.value = '';
    } else if (ctrl && ((!shift && keyCode == h.KeyCode.Y) ||
        (shift && keyCode == h.KeyCode.Z))) { // Ctrl-Y and Ctrl-Shift-Z
      doc.redo();
      ta.value = '';
    } else if (ctrl && !shift && keyCode == h.KeyCode.X) { // Ctrl-X
      removeSelection();
      ta.value = '';
      page.updateAfterPathChange();
    } else if (ctrl && !shift && keyCode == h.KeyCode.C) { // Ctrl-C
      ta.value = '';
    } else if (ctrl && !shift && keyCode == h.KeyCode.V) { // Ctrl-V
      if (selectionStart != selectionEnd) {
        removeSelection();
      }
      paste(ta.value);
      ta.value = '';
      page.updateAfterPathChange();
    } else if (ctrl && shortcuts[keyCode] != null) {
      event.preventDefault();
      shortcuts[keyCode]();
      page.updateAfterPathChange();
    } else if (ta.value != '') {
      String v = ta.value;
      ta.value = '';
      doc.insertNewString(v, shift);
    } else {
      return;
    }
    newTimer();
  }
  
  void blur(h.Event event) {
    hide();
  }
  
  /**
   * Action for the line start key.
   */
  void lineStart() {
    Point pt = selectionStart.positionOnScreen();
    //pt.x = 0;
    // this does not work when blocks are used (it moves the cursor outside)
    DaxeNode dn = selectionStart.dn;
    if (dn == null)
      return;
    while (!dn.block && dn.parent != null)
      dn = dn.parent;
    h.Element hnode = dn.getHTMLNode();
    h.Rectangle rect = hnode.getBoundingClientRect();
    pt.x = rect.left + 1;
    pt.y += 5;
    Position pos = doc.findPosition(pt.x, pt.y);
    if (pos == null)
      return;
    if (pos != null) {
      moveTo(pos);
      page.updateAfterPathChange();
    }
  }
  
  /**
   * Action for the line end key.
   */
  void lineEnd() {
    Point pt = selectionStart.positionOnScreen();
    //pt.x += 10000;
    // this does not work when blocks are used (it moves the cursor outside)
    DaxeNode dn = selectionStart.dn;
    if (dn == null)
      return;
    while (!dn.block && dn.parent != null)
      dn = dn.parent;
    h.Element hnode = dn.getHTMLNode();
    h.Rectangle rect = hnode.getBoundingClientRect();
    pt.x = rect.right - 1;
    pt.y += 5;
    Position pos = doc.findPosition(pt.x, pt.y);
    if (pos == null)
      return;
    if (pos != null) {
      moveTo(pos);
      page.updateAfterPathChange();
    }
  }
  
  /**
   * Action for the left arrow key.
   */
  void left() {
    deSelect();
    selectionStart = previousCaretPosition(selectionStart);
    selectionEnd = new Position.clone(selectionStart);
    updateCaretPosition(true);
    page.updateAfterPathChange();
  }
  
  /**
   * Action for the right arrow key.
   */
  void right() {
    Position end = new Position.clone(selectionEnd);
    end = nextCaretPosition(end);
    deSelect();
    selectionStart = new Position.clone(end);
    selectionEnd = new Position.clone(end);
    updateCaretPosition(true);
    page.updateAfterPathChange();
  }
  
  /**
   * Action for the up arrow key.
   */
  void up() {
    deSelect();
    Point pt = selectionStart.positionOnScreen();
    if (pt == null)
      return;
    Position pos2 = selectionStart;
    while (pos2 == selectionStart) {
      pt.y = pt.y - 7;
      pos2 = doc.findPosition(pt.x, pt.y);
      pos2.moveInsideTextNodeIfPossible();
    }
    if (pos2 != null) {
      selectionStart = pos2;
      selectionEnd = new Position.clone(selectionStart);
    }
    updateCaretPosition(true);
    page.updateAfterPathChange();
  }
  
  /**
   * Action for the down arrow key.
   */
  void down() {
    deSelect();
    Point pt = selectionStart.positionOnScreen();
    if (pt == null)
      return;
    Position pos2 = selectionStart;
    while (pos2 == selectionStart) {
      pt.y = pt.y + 14;
      pos2 = doc.findPosition(pt.x, pt.y);
      pos2.moveInsideTextNodeIfPossible();
    }
    if (pos2 != null) {
      selectionStart = pos2;
      selectionEnd = new Position.clone(selectionStart);
    }
    updateCaretPosition(true);
    page.updateAfterPathChange();
  }
  
  /**
   * Action for the shift + left arrow keys.
   */
  void shiftLeft() {
    Position start = new Position.clone(selectionStart);
    start = previousCaretPosition(start);
    setSelection(start, selectionEnd);
  }
  
  /**
   * Action for the shift + right arrow keys.
   */
  void shiftRight() {
    Position end = new Position.clone(selectionEnd);
    end = nextCaretPosition(end);
    setSelection(selectionStart, end);
  }
  
  /**
   * Action for the page up key.
   */
  void pageUp() {
    Point pt = selectionStart.positionOnScreen();
    if (pt == null)
      return;
    h.DivElement doc1 = h.document.getElementById('doc1'); 
    pt.y -= doc1.offsetHeight;
    Position pos = doc.findPosition(pt.x, pt.y);
    if (pos != null) {
      int initialScroll = doc1.scrollTop;
      moveTo(pos);
      doc1.scrollTop = initialScroll - doc1.offsetHeight;
      page.updateAfterPathChange();
    }
  }
  
  /**
   * Action for the page down key.
   */
  void pageDown() {
    Point pt = selectionStart.positionOnScreen();
    if (pt == null)
      return;
    h.DivElement doc1 = h.document.getElementById('doc1'); 
    pt.y += doc1.offsetHeight;
    Position pos = doc.findPosition(pt.x, pt.y);
    if (pos != null) {
      int initialScroll = doc1.scrollTop;
      moveTo(pos);
      doc1.scrollTop = initialScroll + doc1.offsetHeight;
      page.updateAfterPathChange();
    }
  }
  
  /**
   * Action for the backspace key.
   */
  void backspace() {
    if (selectionStart == selectionEnd) {
      DaxeNode dn = selectionStart.dn;
      int offset = selectionStart.dnOffset;
      if (dn is DNDocument && offset == 0)
        return;
      // if the cursor is at a newline after a node with an automatic (not DOM) newline,
      // the user probably wants to remove the newline instead of the previous node.
      if (dn is DNText && offset == 0 && dn.nodeValue[0] == '\n' &&
          dn.previousSibling != null && dn.previousSibling.newlineAfter()) {
        removeChar(selectionStart);
        return;
      }
      // same thing for newlineInside
      if (dn is DNText && offset == 0 && dn.nodeValue[0] == '\n' &&
          dn.previousSibling == null && dn.parent.newlineInside()) {
        removeChar(selectionStart);
        return;
      }
      // if this is the beginning of a node with no delimiter, remove something
      // before instead of the node with no delimiter (unless it's empty)
      bool justMovedOutOfBlockWithNoDelimiter = false;
      while (dn != null && dn.noDelimiter && offset == 0 && dn.offsetLength > 0) {
        if (dn.noDelimiter && dn.block)
          justMovedOutOfBlockWithNoDelimiter = true;
        else
          justMovedOutOfBlockWithNoDelimiter = false;
        offset = dn.parent.offsetOf(dn);
        dn = dn.parent;
      }
      if (dn is! DNText && offset > 0) {
        DaxeNode prev = dn.childAtOffset(offset - 1);
        if (justMovedOutOfBlockWithNoDelimiter) {
        // if we're at the beginning of a paragraph and the previous element could
        // go inside, move all the previous elements that can inside the paragraph
          DaxeNode next = dn.childAtOffset(offset);
          assert(next.noDelimiter && next.block);
          if (prev.ref != next.ref && !prev.block &&
              ((prev is DNText && doc.cfg.canContainText(next.ref)) ||
                  (prev.ref != null && doc.cfg.isSubElement(next.ref, prev.ref)))) {
            mergeBlockWithPreviousNodes(next);
            return;
          }
        }
        // if the previous node is a paragraph and the next node can move inside,
        // move all the following non-block nodes that can inside.
        if (prev.noDelimiter && prev.block && offset < dn.offsetLength) {
          DaxeNode next = dn.childAtOffset(offset);
          if (prev.ref != next.ref && !next.block &&
              ((next is DNText && doc.cfg.canContainText(prev.ref)) ||
              (next.ref != null && doc.cfg.isSubElement(prev.ref, next.ref)))) {
            mergeBlockWithNextNodes(prev);
            return;
          }
        }
        // move inside previous node with no delimiter, unless 2 paragraphs need to be merged
        if (prev.noDelimiter && (!prev.block ||
            (offset == dn.offsetLength || dn.childAtOffset(offset).ref != prev.ref))) {
          dn = dn.childAtOffset(offset - 1);
          offset = dn.offsetLength;
          if (dn is! DNText && offset > 0)
            prev = dn.childAtOffset(offset - 1);
          else
            prev = null;
        }
      }
      // if this is the end of a node with no delimiter with a character inside,
      // do not remove the whole node, just the last character (except for text nodes)
      while ((dn is! DNText && dn.noDelimiter) && dn.offsetLength == offset &&
          dn.firstChild != null) {
        dn = dn.lastChild;
        offset = dn.offsetLength;
      }
      selectionStart = new Position(dn, offset);
      selectionEnd = new Position.clone(selectionStart);
      selectionStart.move(-1);
      removeChar(selectionStart);
    } else {
      removeSelection();
    }
    page.updateAfterPathChange();
  }
  
  /**
   * Action for the suppr key.
   */
  void suppr() {
    if (selectionStart == selectionEnd) {
      if (selectionStart.dn is DNDocument && selectionStart.dnOffset == selectionStart.dn.offsetLength)
        return;
      DaxeNode dn = selectionStart.dn;
      int offset = selectionStart.dnOffset;
      // if at the end, get out of nodes with no delimiter (unless empty)
      while (dn.noDelimiter && offset == dn.offsetLength && dn.offsetLength > 0) {
        offset = dn.parent.offsetOf(dn) + 1;
        dn = dn.parent;
      }
      if (dn is! DNText && offset > 0 && offset < dn.offsetLength) {
        DaxeNode next = dn.childAtOffset(offset);
        DaxeNode prev = dn.childAtOffset(offset-1);
        // if we're at the end of a paragraph and the next element could
        // go inside, move all the next elements that can inside the paragraph
        if (prev.noDelimiter && prev.block && next.ref != prev.ref && !next.block &&
            ((next is DNText && doc.cfg.canContainText(prev.ref)) ||
                (next.ref != null && doc.cfg.isSubElement(prev.ref, next.ref)))) {
          mergeBlockWithNextNodes(prev);
          return;
        }
        // if the next node is a paragraph and the previous node can move inside,
        // move all the previous non-block nodes that can inside.
        if (next.noDelimiter && next.block && next.ref != prev.ref && !prev.block) {
          if ((prev is DNText && doc.cfg.canContainText(next.ref)) ||
              (prev.ref != null && doc.cfg.isSubElement(next.ref, prev.ref))) {
            mergeBlockWithPreviousNodes(next);
            return;
          }
        }
      }
      // move inside next node with no delimiter unless 2 paragraphs need to be merged
      if (dn is! DNText && offset < dn.offsetLength) {
        DaxeNode next = dn.childAtOffset(offset);
        while (next != null && next.noDelimiter && (!next.block ||
            (offset == 0 || dn.childAtOffset(offset-1).ref != next.ref))) {
          dn = next;
          offset = 0;
          if (dn is! DNText && offset < dn.offsetLength)
            next = dn.childAtOffset(offset);
          else
            next = null;
        }
      }
      selectionStart = new Position(dn, offset);
      selectionEnd = new Position.clone(selectionStart);
      removeChar(selectionStart);
    } else {
      removeSelection();
    }
    page.updateAfterPathChange();
  }
  
  Position nextCaretPosition(Position pos) {
    if (pos.dn is DNDocument && pos.dnOffset == pos.dn.offsetLength)
      return(pos);
    DaxeNode dn = pos.dn;
    int offset = pos.dnOffset;
    // when at the end, get out of non-block nodes with no delimiter
    while (dn != null && dn.noDelimiter && offset == dn.offsetLength && !dn.block) {
      offset = dn.parent.offsetOf(dn) + 1;
      dn = dn.parent;
    }
    // if the node at offset is a text or style, move inside
    DaxeNode nodeAtOffset;
    if (dn.firstChild != null && offset < dn.offsetLength)
      nodeAtOffset = dn.childAtOffset(offset);
    else
      nodeAtOffset = null;
    while (nodeAtOffset != null && nodeAtOffset.noDelimiter && !nodeAtOffset.block) {
      dn = nodeAtOffset;
      offset = 0;
      if (dn.firstChild != null && offset < dn.offsetLength)
        nodeAtOffset = dn.childAtOffset(offset);
      else
        nodeAtOffset = null;
    }
    
    // visible change of position
    // consecutive moves between blocks with no delimiter are not considered cursor moves
    bool noDelimiterBlockMove = false;
    if (offset == dn.offsetLength) {
      // get out of the node
      noDelimiterBlockMove = (dn.noDelimiter && dn.block);
      offset = dn.parent.offsetOf(dn) + 1;
      dn = dn.parent;
      while (noDelimiterBlockMove && dn.noDelimiter && dn.block && offset == dn.offsetLength) {
        // get out of other no delimiter block nodes
        offset = dn.parent.offsetOf(dn) + 1;
        dn = dn.parent;
      }
    } else if (dn is DNText) {
      // move in the text
      offset++;
    } else {
      // enter the node
      dn = dn.childAtOffset(offset);
      // when just entering a node, move to the first cursor position inside
      Position first = dn.firstCursorPositionInside();
      if (first != null) {
        dn = first.dn;
        offset = first.dnOffset;
        noDelimiterBlockMove = (dn.noDelimiter && dn.block);
      } else {
        // if there is none, move after this node
        offset = dn.parent.offsetOf(dn) + 1;
        dn = dn.parent;
      }
    }
    
    // move inside non-block nodes with no delimiter at current offset
    if (dn.firstChild != null && offset < dn.offsetLength)
      nodeAtOffset = dn.childAtOffset(offset);
    else
      nodeAtOffset = null;
    while (nodeAtOffset != null && nodeAtOffset.noDelimiter &&
        (!nodeAtOffset.block || noDelimiterBlockMove)) {
      dn = nodeAtOffset;
      offset = 0;
      if (dn.firstChild != null && offset < dn.offsetLength)
        nodeAtOffset = dn.childAtOffset(offset);
      else
        nodeAtOffset = null;
    }
    return(new Position(dn, offset));
  }
  
  Position previousCaretPosition(Position pos) {
    if (pos.dn is DNDocument && pos.dnOffset == 0)
      return(pos);
    DaxeNode dn = pos.dn;
    int offset = pos.dnOffset;
    // when at the beginning, get out of non-block nodes with no delimiter
    while (dn != null && dn.noDelimiter && offset == 0 && !dn.block) {
      offset = dn.parent.offsetOf(dn);
      dn = dn.parent;
    }
    // if the node before is a text or style, move inside
    DaxeNode nodeBefore;
    if (dn.firstChild != null && offset > 0)
      nodeBefore = dn.childAtOffset(offset - 1);
    else
      nodeBefore = null;
    while (nodeBefore != null && nodeBefore.noDelimiter && !nodeBefore.block) {
      dn = nodeBefore;
      offset = dn.offsetLength;
      if (dn.firstChild != null && offset > 0)
        nodeBefore = dn.childAtOffset(offset - 1);
      else
        nodeBefore = null;
    }
    
    // visible change of position
    // consecutive moves between blocks with no delimiter are not considered cursor moves
    bool noDelimiterBlockMove = false;
    if (offset == 0) {
      // get out of the node
      noDelimiterBlockMove = (dn.noDelimiter && dn.block);
      offset = dn.parent.offsetOf(dn);
      dn = dn.parent;
      while (noDelimiterBlockMove && dn.noDelimiter && dn.block && offset == 0) {
        // get out of other no delimiter block nodes
        offset = dn.parent.offsetOf(dn);
        dn = dn.parent;
      }
    } else if (dn is DNText) {
      // move in the text
      offset--;
    } else {
      // enter the node
      dn = dn.childAtOffset(offset-1);
      offset = dn.offsetLength;
      // when just entering a node, move to the last cursor position inside
      Position last = dn.lastCursorPositionInside();
      if (last != null) {
        dn = last.dn;
        offset = last.dnOffset;
        noDelimiterBlockMove = (dn.noDelimiter && dn.block);
      } else {
        // if there is none, move before this node
        offset = dn.parent.offsetOf(dn);
        dn = dn.parent;
      }
    }
    
    // move inside non-block nodes with no delimiter before current offset
    if (dn.firstChild != null && offset > 0)
      nodeBefore = dn.childAtOffset(offset - 1);
    else
      nodeBefore = null;
    while (nodeBefore != null && nodeBefore.noDelimiter &&
        (!nodeBefore.block || noDelimiterBlockMove)) {
      dn = nodeBefore;
      offset = dn.offsetLength;
      if (dn.firstChild != null && offset > 0)
        nodeBefore = dn.childAtOffset(offset - 1);
      else
        nodeBefore = null;
    }
    return(new Position(dn, offset));
  }
  
  /**
   * Update the caret position when selectionStart == selectionEnd
   */
  void updateCaretPosition(bool scroll) {
    if (selectionEnd != selectionStart)
      return;
    Point pt = selectionStart.positionOnScreen();
    if (pt == null) {
      visible = false;
    } else {
      visible = true;
      h.DivElement doc1 = h.document.getElementById('doc1'); 
      int doctop = doc1.offset.top;
      int docheight = doc1.offset.height;
      if (pt.y - doctop < 0 || pt.y - doctop > docheight) {
        if (scroll) {
          doc1.scrollTop += pt.y.toInt() - doctop;
          pt = selectionStart.positionOnScreen();
        } else {
          visible = false;
        }
      }
    }
    if (visible) {
      caret.style.visibility = 'visible';
      caret.style.top = "${pt.y}px";
      caret.style.left = "${pt.x}px";
      setCaretStyle();
      // move and focus the textarea
      ta.style.top = "${pt.y}px";
      ta.style.left = "${pt.x}px";
      ta.focus();
    } else {
      caret.style.visibility = 'hidden';
    }
  }
  
  /**
   * Sets the caret style (horizontal or vertical)
   */
  void setCaretStyle() {
    bool horizontal; // horizontal caret between block elements
    h.Element hparent = selectionStart.dn.getHTMLNode();
    bool parentBlock = _isBlock(hparent);
    if (parentBlock && selectionStart.dn.offsetLength > 0) {
      bool prevBlock;
      if (selectionStart.dnOffset > 0) {
        DaxeNode prev = selectionStart.dn.childAtOffset(selectionStart.dnOffset - 1);
        h.Element hprev = prev.getHTMLNode();
        prevBlock = _isBlock(hprev);
      } else {
        if (selectionStart.dn is DNWItem)
          prevBlock = false; // special case for the beginning of a WYSIWYG list item
        else
          prevBlock = true;
      }
      bool nextBlock;
      if (selectionStart.dnOffset < selectionStart.dn.offsetLength) {
        DaxeNode next = selectionStart.dn.childAtOffset(selectionStart.dnOffset);
        h.Element hnext = next.getHTMLNode();
        if (next is DNWItem && selectionStart.dnOffset == 0)
          nextBlock = false; // special case for the beginning of a WYSIWYG list
        else
          nextBlock = _isBlock(hnext);
      } else
        nextBlock = true;
      horizontal = prevBlock && nextBlock;
    } else
      horizontal = false;
    if (horizontal)
      caret.classes.add('horizontal');
    else if (caret.classes.contains('horizontal'))
      caret.classes.remove('horizontal');
  }
  
  bool _isBlock(h.Element el) {
    return(el is h.DivElement || el is h.TableElement || el is h.UListElement || el is h.LIElement);
  }
  
  /**
   * Moves the caret to the given Position.
   */
  void moveTo(Position pos) {
    deSelect();
    selectionStart = new Position.clone(pos);
    selectionStart.moveInsideTextNodeIfPossible();
    selectionEnd = new Position.clone(selectionStart);
    updateCaretPosition(true);
  }
  
  /**
   * Hides the cursor.
   */
  void hide() {
    visible = false;
    caret.style.visibility = 'hidden';
  }
  
  /**
   * Shows the cursor.
   */
  void show() {
    if (selectionStart != null && selectionStart == selectionEnd) {
      visible = true;
      caret.style.visibility = 'visible';
    }
  }
  
  /**
   * Obtains the focus.
   */
  void focus() {
    if (visible)
      show();
    ta.focus();
  }
  
  /**
   * Clears the hidden field
   */
  void clearField() {
    ta.value = '';
  }
  
  setSelection(Position start, Position end) {
    if (selectionStart == start && selectionEnd == end) {
      if (start == end) {
        updateCaretPosition(false);
      }
      return;
    }
    deSelect();
    Position previousStart = selectionStart;
    selectionStart = new Position.clone(start);
    selectionEnd = new Position.clone(end);
    if (selectionStart == selectionEnd) {
      //update(selectionStart);
      updateCaretPosition(false);
      page.updateAfterPathChange();
      return;
    }
    if (selectionStart > selectionEnd) {
      Position temp = selectionStart;
      selectionStart = selectionEnd;
      selectionEnd = temp;
    }
    
    // fix selection start and end for styles (different positions look the same for the user)
    // and to keep only the elements entirely inside the selection
    // move the start and end positions out of text and style if possible
    while ((selectionStart.dn is DNText || selectionStart.dn is DNStyle || selectionStart.dn is DNHiddenP) &&
        selectionStart.dnOffset == 0) {
      selectionStart = new Position(selectionStart.dn.parent,
          selectionStart.dn.parent.offsetOf(selectionStart.dn));
    }
    while ((selectionStart.dn is DNText || selectionStart.dn is DNStyle || selectionStart.dn is DNHiddenP) &&
        selectionStart.dnOffset == selectionStart.dn.offsetLength) {
      selectionStart = new Position(selectionStart.dn.parent,
          selectionStart.dn.parent.offsetOf(selectionStart.dn) + 1);
    }
    while ((selectionEnd.dn is DNText || selectionEnd.dn is DNStyle || selectionEnd.dn is DNHiddenP) &&
        selectionEnd.dnOffset == selectionEnd.dn.offsetLength) {
      selectionEnd = new Position(selectionEnd.dn.parent,
          selectionEnd.dn.parent.offsetOf(selectionEnd.dn) + 1);
    }
    while ((selectionEnd.dn is DNText || selectionEnd.dn is DNStyle || selectionEnd.dn is DNHiddenP) &&
        selectionEnd.dnOffset == 0) {
      selectionEnd = new Position(selectionEnd.dn.parent,
          selectionEnd.dn.parent.offsetOf(selectionEnd.dn));
    }
    // now move positions closer if possible
    if (selectionStart != selectionEnd) {
      if ((selectionStart.dn is DNText || selectionStart.dn is DNStyle || selectionStart.dn is DNHiddenP) &&
          selectionStart.dnOffset == selectionStart.dn.offsetLength) {
        DaxeNode next = selectionStart.dn.nextNode();
        selectionStart = new Position(next.parent, next.parent.offsetOf(next));
      }
      if ((selectionEnd.dn is DNText || selectionEnd.dn is DNStyle || selectionEnd.dn is DNHiddenP) &&
          selectionEnd.dnOffset == 0) {
        DaxeNode prev = selectionEnd.dn.previousNode();
        selectionEnd = new Position(prev.parent, prev.parent.offsetOf(prev) + 1);
      }
      bool cont;
      do {
        cont = false;
        if (selectionStart.dn is! DNText && selectionStart.dnOffset < selectionStart.dn.offsetLength) {
          DaxeNode next = selectionStart.dn.childAtOffset(selectionStart.dnOffset);
          if (new Position(selectionStart.dn, selectionStart.dnOffset + 1) > selectionEnd &&
              new Position(next, 0) < selectionEnd) {
            // next is not included and the end is after the beginning of next
            selectionStart = new Position(next, 0);
            cont = true;
          }
        }
      } while (cont);
      do {
        cont = false;
        if (selectionEnd.dn is! DNText && selectionEnd.dnOffset > 0) {
          DaxeNode prev = selectionEnd.dn.childAtOffset(selectionEnd.dnOffset - 1);
          if (new Position(selectionEnd.dn, selectionEnd.dnOffset - 1) < selectionStart &&
              new Position(prev, prev.offsetLength) > selectionStart) {
            // prev is not included and the start is before the end of prev
            selectionEnd = new Position(prev, prev.offsetLength);
            cont = true;
          }
        }
      } while (cont);
    }
    
    if (selectionStart.dn == selectionEnd.dn) {
      DaxeNode dn = selectionStart.dn;
      if (dn.nodeType == DaxeNode.TEXT_NODE) {
        selectText(dn, selectionStart.dnOffset, selectionEnd.dnOffset);
      } else {
        for (int i = selectionStart.dnOffset; i < selectionEnd.dnOffset; i++) {
          DaxeNode child = dn.childAtOffset(i);
          child.setSelected(true);
          selectedNodes.add(child);
        }
      }
    } else {
      DaxeNode startParent = selectionStart.dn;
      if (startParent.nodeType == DaxeNode.TEXT_NODE)
        startParent = startParent.parent;
      if (selectionEnd > new Position(startParent, startParent.offsetLength))
        selectionEnd = new Position(startParent, startParent.offsetLength);
      else {
        DaxeNode endParent = selectionEnd.dn;
        if (endParent.nodeType == DaxeNode.TEXT_NODE)
          endParent = endParent.parent;
        if (endParent != startParent) {
          while (endParent.parent != startParent) {
            endParent = endParent.parent;
          }
          selectionEnd = new Position(startParent, startParent.offsetOf(endParent));
        }
      }
      DaxeNode firstNode;
      if (selectionStart.dn.nodeType == DaxeNode.ELEMENT_NODE ||
          selectionStart.dn.nodeType == DaxeNode.DOCUMENT_NODE) {
        firstNode = selectionStart.dn.childAtOffset(selectionStart.dnOffset);
        if (firstNode != null) {
          Position p2 = new Position(selectionStart.dn, selectionStart.dnOffset + 1);
          if (selectionEnd >= p2) {
            firstNode.setSelected(true);
            selectedNodes.add(firstNode);
          }
        }
      } else {
        firstNode = selectionStart.dn;
        selectText(firstNode, selectionStart.dnOffset, firstNode.offsetLength);
      }
      if (firstNode != null) {
        for (DaxeNode next = firstNode.nextSibling; next != null; next = next.nextSibling) {
          Position p1 = new Position(next.parent, next.parent.offsetOf(next));
          if (p1 < selectionEnd) {
            if (next.nodeType != DaxeNode.TEXT_NODE ||
                selectionEnd >= new Position(next.parent, next.parent.offsetOf(next) + 1)) {
              next.setSelected(true);
              selectedNodes.add(next);
            }
          } else
            break;
        }
      }
      if (selectionEnd.dn.nodeType == DaxeNode.TEXT_NODE) {
        selectText(selectionEnd.dn, 0, selectionEnd.dnOffset);
      }
    }
    if (selectionEnd != selectionStart)
      hide();
    if (selectionStart != previousStart)
      page.updateAfterPathChange();
  }
  
  void selectText(DaxeNode dn, int offset1, int offset2) {
    h.Element parent = dn.getHTMLNode();
    if (parent == null)
      return;
    h.Text n = parent.nodes.first;
    h.Node next = n.nextNode;
    hide();
    String s = dn.nodeValue;
    if (offset1 == 0) {
      n.remove();
    } else {
      n.text = s.substring(0, offset1);
    }
    h.SpanElement span = new h.SpanElement();
    spansSelection.add(span);
    span.classes.add('selection');
    //span.appendText(s.substring(offset1, offset2));
    //see comment in deSelect
    span.append(new h.Text(s.substring(offset1, offset2)));
    if (next == null)
      parent.append(span);
    else
      parent.insertBefore(span, next);
    if (offset2 != s.length) {
      h.Text n3 = new h.Text(s.substring(offset2));
      if (span.nextNode == null)
        parent.append(n3);
      else
        parent.insertBefore(n3, span.nextNode);
    }
  }
  
  void deSelect() {
    for (h.SpanElement span in spansSelection) {
      h.Element parent = span.parent;
      StringBuffer sb = new StringBuffer();
      for (h.Node hn in parent.nodes) {
        sb.write(hn.text);
      }
      parent.nodes.clear();
      // parent.appendText(sb.toString());
      // IE9 replaces \n by BR here when appendText is used
      // http://code.google.com/p/dart/issues/detail?id=11180
      parent.append(new h.Text(sb.toString()));
      selectionEnd = new Position.clone(selectionStart);
      visible = true;
    }
    spansSelection.clear();
    for (DaxeNode dn in selectedNodes) {
      dn.setSelected(false);
    }
    selectedNodes.clear();
    /*
    this is causing too many problems (for instance with undo, or text select)
    a better solution is to make invisible styles visible (see DNStyle)
    if (selectionStart != null && selectionStart == selectionEnd &&
        selectionStart.dn is DNStyle &&
        selectionStart.dn.firstChild == null) {
      // remove an empty style element
      DaxeNode toremove = selectionStart.dn;
      if (toremove.parent != null) { // otherwise it's already been removed
        // we can't do it now, because removing the node can cause text nodes to be merged,
        // and this could change the positions passed in a click
        Timer.run(() {
          print('removed $toremove');
          selectionStart = new Position(toremove.parent, toremove.parent.offsetOf(toremove));
          selectionEnd = new Position.clone(selectionStart);
          doc.removeNode(toremove);
          // TODO: automatically undo the creation and removal of the style element
        });
      }
    }
    */
  }
  
  void newTimer() {
    if (!visible)
      return;
    if (timer != null)
      timer.cancel();
    caret.style.visibility = "visible";
    timer = new Timer.periodic(delay, (Timer t) => caretBlink());
  }
  
  void caretBlink() {
    if (!visible)
      return;
    if (caret.style.visibility == "hidden")
      caret.style.visibility = "visible";
    else if (caret.style.visibility == "visible")
      caret.style.visibility = "hidden";
  }
  
  /**
   * Removes the first character or Daxe node coming after the cursor.
   */
  void removeChar(Position pos) {
    DaxeNode toremove;
    if (pos.dn.nodeType == DaxeNode.TEXT_NODE &&
        pos.dn.offsetLength < pos.dnOffset + 1 &&
        pos.dn.nextSibling != null) {
      // remove the next node
      DaxeNode current = pos.dn;
      DaxeNode next = current.nextSibling;
      while (next == null && current.parent != null) {
        current = current.parent;
        next = current.nextSibling;
      }
      toremove = next;
      if (toremove.nodeType == DaxeNode.TEXT_NODE && toremove.parent != null &&
          toremove.offsetLength == 1)
        toremove = toremove.parent;
    } else if (pos.dn.nodeType == DaxeNode.TEXT_NODE &&
        pos.dn.offsetLength < pos.dnOffset + 1 &&
        pos.dn.nextSibling == null) {
      // remove pos.dn's parent
      toremove = pos.dn;
      if (toremove.parent != null)
        toremove = toremove.parent;
      if (toremove.noDelimiter && toremove.block) {
        if (toremove.nextSibling.ref == toremove.ref) {
          // merge the blocks with no delimiter
          mergeBlocks(toremove, toremove.nextSibling);
        } else {
          // remove something just after this character
          Position after = new Position.clone(pos);
          after.move(1);
          if (after > pos)
            removeChar(after);
        }
        return;
      }
    } else if (pos.dn.nodeType == DaxeNode.ELEMENT_NODE && pos.dn.offsetLength < pos.dnOffset + 1) {
      // remove pos.dn
      toremove = pos.dn;
      if (toremove.noDelimiter && toremove.block) {
        if (toremove.nextSibling != null && toremove.nextSibling.ref == toremove.ref) {
          // merge the blocks with no delimiter
          mergeBlocks(toremove, toremove.nextSibling);
          return;
        }
      }
    } else if (pos.dn.nodeType == DaxeNode.ELEMENT_NODE ||
        pos.dn.nodeType == DaxeNode.DOCUMENT_NODE) {
      toremove = pos.dn.childAtOffset(pos.dnOffset);
      if (toremove.noDelimiter && toremove.block) {
        if (toremove.previousSibling != null && toremove.previousSibling.ref == toremove.ref) {
          // merge the blocks with no delimiter
          mergeBlocks(toremove.previousSibling, toremove);
          return;
        } else if (toremove.offsetLength > 0) {
          // remove something just before this character
          Position before = new Position.clone(pos);
          before.move(-1);
          if (before < pos)
            removeChar(before);
          return;
        }
      }
      if (toremove == null) {
        h.window.alert("I'm sorry Dave, I'm afraid I can't do that.");
        return;
      }
    } else if (pos.dn.nodeType == DaxeNode.TEXT_NODE &&
        pos.dnOffset == 0 && pos.dn.offsetLength == 1 &&
        pos.dn.parent is DNStyle && pos.dn.parent.offsetLength == 1) {
      // remove the style node
      toremove = pos.dn.parent;
      while (toremove.parent is DNStyle && toremove.parent.offsetLength == 1)
        toremove = toremove.parent;
    } else {
      doc.removeString(pos, 1);
      // merge styles if possible
      EditAndNewPositions ep = DNStyle.mergeAt(selectionStart);
      if (ep != null) {
        doc.doNewEdit(ep.edit);
        doc.combineLastEdits(Strings.get('undo.remove_text'), 2);
        setSelection(ep.start, ep.end);
      }
      return;
    }
    if (toremove is DNWItem && toremove.parent.offsetLength == 1) {
      // remove the whole DNWList when the last DNWItem inside is removed
      toremove = toremove.parent;
    }
    if (!toremove.userCannotRemove) {
      doc.removeNode(toremove);
      // merge styles if possible
      EditAndNewPositions ep = DNStyle.mergeAt(selectionStart);
      if (ep != null) {
        doc.doNewEdit(ep.edit);
        doc.combineLastEdits(Strings.get('undo.remove_element'), 2);
        setSelection(ep.start, ep.end);
      }
    }
  }
  
  /**
   * Removes everything inside the current selection.
   */
  void removeSelection() {
    if (selectionStart == selectionEnd)
      return;
    Position start = new Position.clone(selectionStart);
    Position end = new Position.clone(selectionEnd);
    deSelect();
    if (start.dn is DNWList && start.dn == end.dn && start.dnOffset == 0 &&
        end.dnOffset == end.dn.offsetLength) {
      // all DNWItem will be removed, the whole DNWList must be removed instead
      doc.removeNode(start.dn);
    } else {
      doc.removeBetween(start, end);
      // merge styles if possible
      EditAndNewPositions ep = DNStyle.mergeAt(start);
      if (ep != null) {
        doc.doNewEdit(ep.edit);
        doc.combineLastEdits(Strings.get('undo.remove'), 2);
        setSelection(ep.start, ep.end);
      }
    }
  }
  
  /**
   * Refresh display
   */
  void refresh() {
    Position start = selectionStart;
    Position end = selectionEnd;
    selectionStart = null;
    selectionEnd = null;
    setSelection(start, end);
  }
  
  /**
   * Returns the current XML selection as a String.
   */
  String copy() {
    StringBuffer sb = new StringBuffer();
    if (selectionStart.dn == selectionEnd.dn) {
      DaxeNode dn = selectionStart.dn;
      if (dn.nodeType == DaxeNode.TEXT_NODE) {
        sb.write(dn.nodeValue.substring(selectionStart.dnOffset, selectionEnd.dnOffset));
      } else {
        for (int i = selectionStart.dnOffset; i < selectionEnd.dnOffset; i++) {
          DaxeNode child = dn.childAtOffset(i);
          sb.write(child);
        }
      }
    } else {
      DaxeNode firstNode;
      if (selectionStart.dn.nodeType == DaxeNode.ELEMENT_NODE) {
        firstNode = selectionStart.dn.childAtOffset(selectionStart.dnOffset);
        Position p2 = new Position(selectionStart.dn, selectionStart.dnOffset + 1);
        if (selectionEnd >= p2) {
          sb.write(firstNode);
        }
      } else {
        firstNode = selectionStart.dn;
        sb.write(firstNode.nodeValue.substring(selectionStart.dnOffset));
      }
      for (DaxeNode next = firstNode.nextSibling; next != null; next = next.nextSibling) {
        Position p1 = new Position(next.parent, next.parent.offsetOf(next));
        if (p1 < selectionEnd) {
          if (next.nodeType != DaxeNode.TEXT_NODE ||
              selectionEnd >= new Position(next.parent, next.parent.offsetOf(next) + 1)) {
            sb.write(next);
            next.setSelected(true);
          }
        } else
          break;
      }
      if (selectionEnd.dn.nodeType == DaxeNode.TEXT_NODE) {
        sb.write(selectionEnd.dn.nodeValue.substring(0, selectionEnd.dnOffset));
      }
    }
    return(sb.toString());
  }
  
  /**
   * Parses the given String and pastes the XML at the current position.
   */
  bool paste(String s) {
    x.Document tmpdoc;
    try {
      x.DOMParser dp = new x.DOMParser();
      tmpdoc = dp.parseFromString("<root>$s</root>");
    } on x.DOMException catch(ex) {
      // this is not XML, it is inserted as string if it is possible
      bool problem = false;
      if (s.trim() != '') {
        DaxeNode parent = selectionStart.dn;
        if (parent.nodeType == DaxeNode.TEXT_NODE)
          parent = parent.parent;
        if (parent.nodeType == DaxeNode.DOCUMENT_NODE)
          problem = true;
        else if (parent.ref != null && !doc.cfg.canContainText(parent.ref))
          problem = true;
      }
      if (problem) {
        h.window.alert(Strings.get('insert.text_not_allowed'));
        return(false);
      }
      doc.insertString(selectionStart, s);
      return(true);
    }
    DaxeNode parent = selectionStart.dn;
    if (parent is DNText)
      parent = parent.parent;
    x.Element root = tmpdoc.documentElement;
    // to call fixLineBreaks(), we need a real DaxeNode for the "root", with the right ref
    DaxeNode dnRoot = NodeFactory.create(parent.ref);
    if (root.childNodes != null) {
      for (x.Node n in root.childNodes) {
        DaxeNode dn = NodeFactory.createFromNode(n, dnRoot);
        dnRoot.appendChild(dn);
      }
    }
    dnRoot.fixLineBreaks();
    if (doc.hiddenParaRefs != null) {
      // add or remove hidden paragraphs where necessary
      DNHiddenP.fixFragment(parent, dnRoot);
      doc.removeWhitespaceForHiddenParagraphs(dnRoot);
    }
    UndoableEdit edit = new UndoableEdit.compound(Strings.get('undo.paste'));
    try {
      edit.addSubEdit(doc.insertChildrenEdit(dnRoot, selectionStart, checkValidity:true));
    } on DaxeException catch (ex) {
      h.window.alert(ex.toString());
      return(false);
    }
    doc.doNewEdit(edit);
    return(true);
  }
  
  void mergeBlocks(DaxeNode dn1, DaxeNode dn2) {
    UndoableEdit edit = new UndoableEdit.compound(Strings.get('undo.remove_text'));
    DaxeNode clone;
    Position clonep1 = new Position(dn2, 0);
    Position clonep2 = new Position(dn2, dn2.offsetLength);
    if (clonep2 > clonep1)
      clone = doc.cloneBetween(clonep1, clonep2);
    else
      clone = null;
    edit.addSubEdit(new UndoableEdit.removeNode(dn2));
    if (clone != null)
      edit.addSubEdit(doc.insertChildrenEdit(clone, new Position(dn1, dn1.offsetLength)));
    Position futureCursorPos;
    if (dn1.lastChild is DNText)
      futureCursorPos = new Position(dn1.lastChild, dn1.lastChild.offsetLength);
    else
      futureCursorPos = new Position(dn1, dn1.offsetLength);
    doc.doNewEdit(edit);
    page.moveCursorTo(futureCursorPos);
  }
  
  void mergeBlockWithPreviousNodes(DaxeNode dn) {
    assert(dn.previousSibling != null);
    int offset = dn.parent.offsetOf(dn);
    UndoableEdit edit = new UndoableEdit.compound(Strings.get('undo.remove_text'));
    // clone the nodes that will move into the paragraph
    int startOffset = offset;
    bool withText = doc.cfg.canContainText(dn.ref);
    while (startOffset > 0) {
      DaxeNode child = dn.parent.childAtOffset(startOffset-1);
      if (child.block || (child is DNText && !withText) ||
          (child.ref != null && !doc.cfg.isSubElement(dn.ref, child.ref)))
        break;
      startOffset--;
    }
    Position pStart = new Position(dn.parent, startOffset);
    Position currentPos = new Position(dn.parent, offset);
    assert (pStart < currentPos);
    DaxeNode cloneLeft = doc.cloneCutBetween(dn.parent, pStart, currentPos);
    edit.addSubEdit(doc.removeBetweenEdit(pStart, currentPos));
    edit.addSubEdit(doc.insertChildrenEdit(cloneLeft, new Position(dn, 0)));
    Position futureCursorPos = new Position(dn, 0);
    futureCursorPos.moveInsideTextNodeIfPossible();
    futureCursorPos = new Position.rightOffsetPosition(futureCursorPos);
    doc.doNewEdit(edit);
    moveTo(futureCursorPos);
    page.updateAfterPathChange();
    return;
  }
  
  void mergeBlockWithNextNodes(DaxeNode dn) {
    assert(dn.nextSibling != null);
    int offset = dn.parent.offsetOf(dn.nextSibling);
    UndoableEdit edit = new UndoableEdit.compound(Strings.get('undo.remove_text'));
    // clone the nodes that will move into the paragraph
    int endOffset = offset;
    bool withText = doc.cfg.canContainText(dn.ref);
    while (endOffset < dn.parent.offsetLength) {
      DaxeNode child = dn.parent.childAtOffset(endOffset);
      if (child.block || (child is DNText && !withText) ||
          (child.ref != null && !doc.cfg.isSubElement(dn.ref, child.ref)))
        break;
      endOffset++;
    }
    Position pEnd = new Position(dn.parent, endOffset);
    Position currentPos = new Position(dn.parent, offset);
    assert (currentPos < pEnd);
    DaxeNode cloneRight = doc.cloneCutBetween(dn.parent, currentPos, pEnd);
    edit.addSubEdit(doc.removeBetweenEdit(currentPos, pEnd));
    edit.addSubEdit(doc.insertChildrenEdit(cloneRight, new Position(dn, dn.offsetLength)));
    Position futureCursorPos = new Position(dn, dn.offsetLength);
    futureCursorPos.moveInsideTextNodeIfPossible();
    futureCursorPos = new Position.leftOffsetPosition(futureCursorPos);
    doc.doNewEdit(edit);
    moveTo(futureCursorPos);
    page.updateAfterPathChange();
    return;
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/daxe_attr.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/daxe_attr.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

/**
 * An attribute.
 */
class DaxeAttr {
  String namespaceURI;
  String prefix;
  String localName;
  String value;
  
  DaxeAttr.fromNode(x.Attr a) {
    namespaceURI = a.namespaceURI;
    prefix = a.prefix;
    if (a.prefix != null)
      localName = a.localName;
    else
      localName = a.name;
    value = a.nodeValue;
  }
  
  DaxeAttr(String name, String value) {
    namespaceURI = null;
    prefix = null;
    localName = name;
    this.value = value;
  }
  
  DaxeAttr.NS(String namespace, String qualifiedName, String value) {
    namespaceURI = namespace;
    int ind = qualifiedName.indexOf(':');
    if (ind == -1) {
      prefix = null;
      localName = qualifiedName;
    } else {
      prefix = qualifiedName.substring(0, ind);
      localName = qualifiedName.substring(ind + 1);
    }
    this.value = value;
  }
  
  DaxeAttr.clone(DaxeAttr attr) {
    namespaceURI = attr.namespaceURI;
    prefix = attr.prefix;
    localName = attr.localName;
    value = attr.value;
  }
  
  
  String get name {
    if (prefix == null)
      return(localName);
    else
      return("$prefix:$localName");
  }
  
  String toString() {
    StringBuffer sb = new StringBuffer();
    if (prefix != null) {
      sb.write(prefix);
      sb.write(':');
    }
    sb.write(localName);
    sb.write('="');
    sb.write(DaxeNode.escape(value));
    sb.write('"');
    return(sb.toString());
  }
}


Index: modules/damieng/graphical_editor/daxe/lib/src/daxe_document.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/daxe_document.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

/**
 * The interface for an XML document.
 */
class DaxeDocument {
  int _id_count = 0;
  Map<String, DaxeNode> _idToJN = new Map<String, DaxeNode>();
  DNDocument dndoc;
  Config cfg;
  List<UndoableEdit> edits = new List<UndoableEdit>();
  int undoPosition = -1;
  String filePath;
  String saveURL;
  List<x.Element> hiddenParaRefs; /* references for hidden paragraphs */
  x.Element hiddendiv;
  
  /**
   * Create a new document with the given configuration path.
   */
  Future newDocument(String configPath) {
    Completer completer = new Completer();
    cfg = new Config();
    cfg.load(configPath).then((_) {
      hiddenParaRefs = cfg.elementsWithType('hiddenp');
      if (hiddenParaRefs.length == 0)
        hiddenParaRefs = null;
      hiddendiv = cfg.firstElementWithType('hiddendiv');
      filePath = null;
      dndoc = new DNDocument();
      List<x.Element> roots = cfg.rootElements();
      if (roots.length == 1) {
        DaxeNode root = NodeFactory.create(roots[0]);
        cfg.addNamespaceAttributes(root);
        dndoc.appendChild(root);
        root.updateValidity();
      }
      completer.complete();
    }, onError: (DaxeException ex) {
      completer.completeError(ex);
    });
    return(completer.future);
  }
  
  /**
   * Open the document at filePath with the given configuration path.
   * If a 404 occurs, create a new document with the config instead
   * (so that it can be saved at the given path)
   */
  Future openDocument(String filePath, String configPath, {bool removeIndents: true}) {
    Completer completer = new Completer();
    cfg = new Config();
    cfg.load(configPath).then((_) {
      hiddenParaRefs = cfg.elementsWithType('hiddenp');
      if (hiddenParaRefs.length == 0)
        hiddenParaRefs = null;
      hiddendiv = cfg.firstElementWithType('hiddendiv');
      this.filePath = filePath;
      x.DOMParser dp = new x.DOMParser();
      dp.parseFromURL(filePath).then((x.Document xmldoc) {
        if (removeIndents)
          removeWhitespace(xmldoc.documentElement);
        dndoc = NodeFactory.createFromNode(xmldoc, null);
        if (cfg != null && hiddenParaRefs != null)
          removeWhitespaceForHiddenParagraphs(dndoc);
        completer.complete();
      }, onError: (x.DOMException ex) {
        if (ex.errorCode == 404) {
          dndoc = new DNDocument();
          List<x.Element> roots = cfg.rootElements();
          if (roots.length == 1) {
            DaxeNode root = NodeFactory.create(roots[0]);
            cfg.addNamespaceAttributes(root);
            dndoc.appendChild(root);
            root.updateValidity();
          }
          completer.complete();
        } else
          completer.completeError(new DaxeException("Opening $filePath: $ex"));
      });
    }, onError: (DaxeException ex) {
      completer.completeError(new DaxeException("Reading config $configPath: $ex"));
    });
    return(completer.future);
  }
  
  /**
   * Send the document with a POST request to saveURL.
   */
  Future saveOnWebJaxe() {
    assert(saveURL != null);
    Completer completer = new Completer();
    String bound = 'AaB03x';
    h.HttpRequest request = new h.HttpRequest();
    request.onLoad.listen((h.ProgressEvent event) {
      String response = request.responseText;
      if (response.startsWith('ok'))
        completer.complete();
      else {
        String errorMessage;
        if (response.startsWith('erreur\n'))
          errorMessage = response.substring('erreur\n'.length);
        else
          errorMessage = response;
        completer.completeError(new DaxeException(errorMessage));
      }
    });
    request.onError.listen((h.ProgressEvent event) {
      completer.completeError(new DaxeException(request.status.toString()));
    });
    request.open('POST', saveURL);
    request.setRequestHeader('Content-Type', "multipart/form-data; boundary=$bound");
    
    StringBuffer sb = new StringBuffer();
    sb.write("--$bound\r\n");
    sb.write('Content-Disposition: form-data; name="chemin"\r\n');
    sb.write('Content-type: text/plain; charset=UTF-8\r\n');
    sb.write('Content-transfer-encoding: 8bit\r\n\r\n');
    sb.write(filePath);
    sb.write("\r\n--$bound\r\n");
    sb.write('Content-Disposition: form-data; name="contenu"; filename="$filePath"\r\n');
    sb.write('Content-Type: application/octet-stream\r\n\r\n');
    dndoc.xmlEncoding = 'UTF-8'; // the document is forced to use UTF-8
    sb.write(toString());
    sb.write('\r\n--$bound--\r\n\r\n');
    request.send(sb.toString());
    
    /*
    // to use the document encoding (requires the convert library):
    // (need support for ArrayBuffer in IE, does not work in IE9)
    StringBuffer sb = new StringBuffer();
    sb.write("--$bound\r\n");
    sb.write('Content-Disposition: form-data; name="chemin"\r\n');
    sb.write('Content-type: text/plain; charset=UTF-8\r\n');
    sb.write('Content-transfer-encoding: 8bit\r\n\r\n');
    sb.write(filePath);
    sb.write("\r\n--$bound\r\n");
    sb.write('Content-Disposition: form-data; name="contenu"; filename="$filePath"\r\n');
    sb.write('Content-Type: application/octet-stream\r\n\r\n');
    List<int> buffer = UTF8.encode(sb.toString());
    Encoding encoding = Encoding.getByName(dndoc.xmlEncoding);
    if (encoding == null) {
      print('encoding not supported by Dart: ${dndoc.xmlEncoding} - using UTF-8 instead');
      dndoc.xmlEncoding = 'UTF-8';
      encoding = UTF8;
    }
    buffer.addAll(encoding.encode(toString()));
    buffer.addAll(UTF8.encode('\r\n--$bound--\r\n\r\n'));
    request.send(buffer); // what buffer type can I use here ???
    */
    return(completer.future);
  }
  
  String newId(DaxeNode jn) {
    _id_count++;
    String sid = "a$_id_count";
    _idToJN[sid] = jn;
    return(sid);
  }
  
  DaxeNode getNodeById(String id) {
    if (id == null)
      return(null);
    return(_idToJN[id]);
  }
  
  h.Element html() {
    return(dndoc.html());
  }
  
  DaxeNode getRootElement() {
    for (DaxeNode dn=dndoc.firstChild; dn != null; dn=dn.nextSibling) {
      if (dn.nodeType == DaxeNode.ELEMENT_NODE)
        return(dn);
    }
    return(null);
  }
  
  /**
   * Inserts a new [DaxeNode] at the given [Position].
   * The insert is added to the document history so that it can be undone.
   */
  void insertNode(DaxeNode dn, Position pos) {
    UndoableEdit edit = new UndoableEdit.insertNode(pos, dn);
    doNewEdit(edit);
  }
  
  /**
   * Removes the given [DaxeNode] from the document.
   * The removal is added to the document history so that it can be undone.
   */
  void removeNode(DaxeNode dn) {
    UndoableEdit edit = new UndoableEdit.removeNode(dn);
    doNewEdit(edit);
  }
  
  /**
   * Inserts a String at the given [Position].
   * The insert is added to the document history so that it can be undone.
   */
  void insertString(Position pos, String s) {
    UndoableEdit edit = new UndoableEdit.insertString(pos, s);
    doNewEdit(edit);
  }
  
  /**
   * Removes a String with the given [length] at the given [Position].
   * The removal is added to the document history so that it can be undone.
   */
  void removeString(Position pos, int length) {
    UndoableEdit edit = new UndoableEdit.removeString(pos, length);
    doNewEdit(edit);
  }
  
  /**
   * Removes everything between [start] and [end].
   * The two positions must not cut a node, except for text nodes
   * (all the document elements must be either inside or outside the range).
   * The removal is added to the document history so that it can be undone.
   */
  void removeBetween(Position start, Position end) {
    UndoableEdit edit = removeBetweenEdit(start, end);
    doNewEdit(edit);
  }
  
  /**
   * Returns an edit to Remove everything between [start] and [end].
   * The two positions must not cut a node, except for text nodes
   * (all the document elements must be either inside or outside the range).
   */
  UndoableEdit removeBetweenEdit(Position start, Position end) {
    assert(start < end);
    UndoableEdit edit = new UndoableEdit.compound(Strings.get('undo.remove'));
    if (start.dn == end.dn) {
      DaxeNode dn = start.dn;
      if (dn.nodeType == DaxeNode.TEXT_NODE) {
        edit.addSubEdit(new UndoableEdit.removeString(
            new Position(dn, start.dnOffset),
            end.dnOffset - start.dnOffset));
      } else {
        // text nodes have to be removed first to avoid text merging problems
        for (int i = start.dnOffset; i < end.dnOffset; i++) {
          if (dn.childAtOffset(i) is DNText)
            edit.addSubEdit(new UndoableEdit.removeNode(dn.childAtOffset(i)));
        }
        for (int i = start.dnOffset; i < end.dnOffset; i++) {
          if (dn.childAtOffset(i) is! DNText)
            edit.addSubEdit(new UndoableEdit.removeNode(dn.childAtOffset(i)));
        }
      }
    } else {
      bool removeFirstNode = false;
      // beginning of the selection
      DaxeNode firstNode;
      if (start.dn.nodeType == DaxeNode.ELEMENT_NODE) {
        firstNode = start.dn.childAtOffset(start.dnOffset);
        Position p2 = new Position(start.dn, start.dnOffset + 1);
        if (end >= p2)
          removeFirstNode = true;
      } else {
        firstNode = start.dn;
        if (firstNode.offsetLength - start.dnOffset > 0) {
          edit.addSubEdit(new UndoableEdit.removeString(
              new Position(firstNode, start.dnOffset),
              firstNode.offsetLength - start.dnOffset));
        }
      }
      // end of the selection
      // the nodes are removed at the end to avoid text merge problems
      if (end.dn.nodeType == DaxeNode.TEXT_NODE && end.dnOffset > 0) {
        edit.addSubEdit(new UndoableEdit.removeString(
            new Position(end.dn, 0), end.dnOffset));
      }
      // between the first and the end node of the selection
      // text nodes to remove have to be removed before other nodes to avoid being merged
      for (DaxeNode next = firstNode.nextSibling; next != null; next = next.nextSibling) {
        Position p1 = new Position(next.parent, next.parent.offsetOf(next));
        if (p1 < end) {
          if (next.nodeType == DaxeNode.TEXT_NODE &&
              end >= new Position(next.parent, next.parent.offsetOf(next) + 1)) {
            edit.addSubEdit(new UndoableEdit.removeNode(next));
          }
        } else
          break;
      }
      if (removeFirstNode)
        edit.addSubEdit(new UndoableEdit.removeNode(firstNode));
      for (DaxeNode next = firstNode.nextSibling; next != null; next = next.nextSibling) {
        Position p1 = new Position(next.parent, next.parent.offsetOf(next));
        if (p1 < end) {
          if (next.nodeType != DaxeNode.TEXT_NODE) {
            edit.addSubEdit(new UndoableEdit.removeNode(next));
          }
        } else
          break;
      }
    }
    return(edit);
  }
  
  /**
   * Returns a root DaxeNode with a clone of everything between [start] and [end].
   * The two positions must not cut a node, except for text nodes
   * (all the document elements must be either inside or outside the range).
   */
  DaxeNode cloneBetween(Position start, Position end) {
    assert(start < end);
    DaxeNode parent = start.dn;
    if (parent is DNText)
      parent = parent.parent;
    DaxeNode root = NodeFactory.create(parent.ref);
    if (start.dn == end.dn) {
      DaxeNode dn = start.dn;
      if (dn.nodeType == DaxeNode.TEXT_NODE) {
        root.appendChild(new DNText(dn.nodeValue.substring(start.dnOffset, end.dnOffset)));
      } else {
        for (int i = start.dnOffset; i < end.dnOffset; i++) {
          root.appendChild(new DaxeNode.clone(dn.childAtOffset(i)));
        }
      }
    } else {
      // beginning of the selection
      DaxeNode firstNode;
      if (start.dn.nodeType == DaxeNode.ELEMENT_NODE) {
        firstNode = start.dn.childAtOffset(start.dnOffset);
        Position p2 = new Position(start.dn, start.dnOffset + 1);
        if (end >= p2) {
          root.appendChild(new DaxeNode.clone(firstNode));
        }
      } else {
        firstNode = start.dn;
        if (firstNode.offsetLength - start.dnOffset > 0) {
          root.appendChild(new DNText(
              firstNode.nodeValue.substring(start.dnOffset, firstNode.offsetLength)));
        }
      }
      // between the first and the end node of the selection
      for (DaxeNode next = firstNode.nextSibling; next != null; next = next.nextSibling) {
        Position p1 = new Position(next.parent, next.parent.offsetOf(next));
        if (p1 < end) {
          if (next.nodeType != DaxeNode.TEXT_NODE ||
              (next.nodeType == DaxeNode.TEXT_NODE &&
              end >= new Position(next.parent, next.parent.offsetOf(next) + 1))) {
            root.appendChild(new DaxeNode.clone(next));
          }
        } else
          break;
      }
      // end of the selection
      if (end.dn.nodeType == DaxeNode.TEXT_NODE && end.dnOffset > 0) {
        root.appendChild(new DNText(end.dn.nodeValue.substring(0, end.dnOffset)));
      }
    }
    return(root);
  }
  
  /**
   * Returns a cloned node of root including everything between p1 and p2.
   * The interval p1-p2 may cut nodes.
   * The root attributes are preserved.
   */
  DaxeNode cloneCutBetween(DaxeNode root, Position p1, Position p2) {
    while (p1.dnOffset == p1.dn.offsetLength && p1 < p2) {
      // to avoid cloning an empty element at the beginning
      p1 = new Position(p1.dn.parent, p1.dn.parent.offsetOf(p1.dn) + 1);
    }
    while (p2.dnOffset == 0 && p2 > p1) {
      // to avoid cloning an empty element at the end
      p2 = new Position(p2.dn.parent, p2.dn.parent.offsetOf(p2.dn));
    }
    DaxeNode root2 = new DaxeNode.clone(root);
    // optimization: could this shallow clone be optimized without requiring another method for several DaxeNodes ?
    // (or we could reuse the children later instead of recloning them)
    for (DaxeNode dn = root2.firstChild; dn != null; dn = root2.firstChild)
      root2.removeChild(dn);
    int offset = 0;
    for (DaxeNode dn=root.firstChild; dn != null; dn=dn.nextSibling) {
      Position dnstart = new Position(root, offset);
      Position dnend = new Position(root, offset + 1);
      if (dnstart >= p1 && dnend <= p2) {
        DaxeNode dn2 = new DaxeNode.clone(dn);
        root2.appendChild(dn2);
      } else if (dnend <= p1 || dnstart >= p2) {
        // dn is not included at all in the selection
      } else {
        if (dn is DNText) {
          if (dnstart > p1 && dnend > p2) {
            assert(dn == p2.dn);
            if (0 < p2.dnOffset)
              root2.appendChild(new DNText(dn.nodeValue.substring(0, p2.dnOffset)));
          } else if (dnstart < p1 && dnend < p2) {
            assert(dn == p1.dn);
            if (p1.dnOffset < dn.offsetLength)
              root2.appendChild(new DNText(dn.nodeValue.substring(p1.dnOffset, dn.offsetLength)));
          } else {
            // dnstart <= p1 && dnend >= p2
            int offset1;
            if (p1.dn == dn)
              offset1 = p1.dnOffset;
            else
              offset1 = 0;
            int offset2;
            if (p2.dn == dn)
              offset2 = p2.dnOffset;
            else
              offset2 = dn.offsetLength;
            if (offset1 < offset2)
              root2.appendChild(new DNText(dn.nodeValue.substring(offset1, offset2)));
          }
        } else {
          DaxeNode dn2 = cloneCutBetween(dn, p1, p2);
          root2.appendChild(dn2);
        }
      }
      offset++;
    }
    return(root2);
  }
  
  /**
   * Returns an edit to insert all the children of [root] at position [pos].
   * Throws a DaxeException on error.
   */
  UndoableEdit insertChildrenEdit(DaxeNode root, Position pos, {bool checkValidity: true}) {
    if (pos.dn is DNText && pos.dnOffset == 0) // this position might move
      pos = new Position(pos.dn.parent, pos.dn.parent.offsetOf(pos.dn));
    DaxeNode parent = pos.dn;
    int offset = pos.dnOffset;
    if (parent is DNText) {
      offset = parent.parent.offsetOf(parent);
      parent = parent.parent;
    }
    UndoableEdit edit = new UndoableEdit.compound('insertChildren');
    
    // extract children from the root to avoid text merging under root
    List<DaxeNode> children = new List<DaxeNode>();
    while (root.firstChild != null) {
      children.add(root.firstChild);
      root.removeChild(root.firstChild);
    }
    // inserting non-text nodes first to avoid text merging issues in the target
    Position insertPos = new Position.clone(pos);
    for (DaxeNode dn in children) {
      if (dn is! DNText) {
        if (checkValidity &&
            (parent is DNComment || parent is DNCData || parent is DNProcessingInstruction)) {
          String title;
          if (dn.ref == null)
            title = dn.nodeName;
          else
            title = cfg.elementTitle(dn.ref);
          throw new DaxeException(title + ' ' + Strings.get('insert.not_authorized_here'));
        }
        if (dn is DNCData) {
          if (checkValidity && parent.nodeType == DaxeNode.DOCUMENT_NODE)
            throw new DaxeException(Strings.get('insert.text_not_allowed'));
          String value = null;
          if (dn.firstChild != null)
            value = dn.firstChild.nodeValue;
          if (value == null)
            value = '';
          if (checkValidity && value.trim() != '' && !cfg.canContainText(parent.ref)) {
            throw new DaxeException(Strings.get('insert.text_not_allowed'));
          }
          edit.addSubEdit(new UndoableEdit.insertNode(insertPos, dn));
        } else {
          if (checkValidity) {
            if (parent.nodeType == DaxeNode.DOCUMENT_NODE) {
              if (!cfg.rootElements().contains(dn.ref))
                throw new DaxeException(cfg.elementTitle(dn.ref) + ' ' + Strings.get('insert.not_authorized_here'));
            } else if (dn is! DNComment && dn is! DNProcessingInstruction) {
              if (dn.ref == null || !cfg.isSubElement(parent.ref, dn.ref)) {
                String title;
                if (dn.ref == null)
                  title = dn.nodeName;
                else
                  title = cfg.elementTitle(dn.ref);
                String parentTitle = cfg.elementTitle(parent.ref);
                throw new DaxeException(title + ' ' + Strings.get('insert.not_authorized_inside') + ' ' + parentTitle);
              }
              if (!cfg.insertIsPossible(parent, offset, offset, dn.ref)) {
                throw new DaxeException(cfg.elementTitle(dn.ref) + ' ' + Strings.get('insert.not_authorized_here'));
              }
            }
          }
          edit.addSubEdit(new UndoableEdit.insertNode(insertPos, dn));
        }
        if (insertPos.dn is DNText) // after first insert inside a text node
          insertPos = new Position(parent, offset + 2);
        else
          insertPos = new Position(parent, insertPos.dnOffset + 1);
      }
    }
    // Now copy text nodes.
    // If the first text node merges to the left, it will be taken into
    // account for the insert positions of the following text nodes.
    bool merge = false;
    bool first = true;
    for (DaxeNode dn in children) {
      if (dn is DNText) {
        if (checkValidity && parent.nodeType == DaxeNode.DOCUMENT_NODE)
          throw new DaxeException(Strings.get('insert.text_not_allowed'));
        String value = dn.nodeValue;
        if (value == null)
          value = '';
        if (checkValidity && value.trim() != '' && !cfg.canContainText(parent.ref)) {
          throw new DaxeException(Strings.get('insert.text_not_allowed'));
        }
        int insertOffset = offset + children.indexOf(dn);
        if (pos.dn is DNText)
          insertOffset++;
        if (merge)
          insertOffset--;
        if (children.length == 1)
          insertPos = pos; // case of a single text node
        else
          insertPos = new Position(parent, insertOffset);
        if (first) {
          if (insertOffset > 0 && (pos.dn is DNText ||
              parent.childAtOffset(insertOffset - 1) is DNText))
            merge = true;
          first = false;
        }
        edit.addSubEdit(new UndoableEdit.insertString(insertPos, dn.nodeValue));
      }
    }
    return(edit);
  }
  
  String toString() {
    return(dndoc.toString());
  }
  
  /**
   * Returns a new DOM document matching this document.
   */
  x.Document toDOMDoc() {
    x.DOMImplementation domimpl = new x.DOMImplementationImpl();
    x.Document domdoc = domimpl.createDocument(null, null, null);
    dndoc.toDOMNode(domdoc);
    return(domdoc);
  }
  
  /**
   * Executes a new edit and adds it to the history so that it can be undone.
   */
  void doNewEdit(UndoableEdit edit) {
    edit.doit();
    if (undoPosition < edits.length - 1)
      edits.removeRange(undoPosition + 1, edits.length);
    if (undoPosition < 0 || !edits[undoPosition].addEdit(edit)) {
      edits.add(edit);
      undoPosition++;
    }
    page.updateUndoMenus();
  }
  
  void undo() {
    if (undoPosition < 0)
      return;
    UndoableEdit edit = edits[undoPosition];
    edit.undo();
    undoPosition--;
    page.updateUndoMenus();
    page.updateAfterPathChange();
  }
  
  void redo() {
    if (undoPosition >= edits.length - 1)
      return;
    UndoableEdit edit = edits[undoPosition + 1];
    edit.doit();
    undoPosition++;
    page.updateUndoMenus();
    page.updateAfterPathChange();
  }
  
  bool isUndoPossible() {
    return(undoPosition >= 0);
  }
  
  bool isRedoPossible() {
    return(undoPosition < edits.length - 1);
  }
  
  /**
   * Returns the title for the undo menu.
   */
  String getUndoTitle() {
    String title;
    if (undoPosition >= 0)
      title = edits[undoPosition].title;
    else
      title = null;
    if (title == null)
      return(Strings.get('undo.undo'));
    else
      return("${Strings.get('undo.undo')} $title");
  }
  
  /**
   * Returns the title for the redo menu.
   */
  String getRedoTitle() {
    String title;
    if (undoPosition < edits.length - 1)
      title = edits[undoPosition + 1].title;
    else
      title = null;
    if (title == null)
      return(Strings.get('undo.redo'));
    else
      return("${Strings.get('undo.redo')} $title");
  }
  
  /**
   * Combine the last nb edits into a single compound edit.
   */
  void combineLastEdits(String title, int nb) {
    UndoableEdit edit = new UndoableEdit.compound(title);
    for (int i=edits.length-nb; i<edits.length; i++)
      edit.addSubEdit(edits[i]);
    edits.removeRange(edits.length - nb, edits.length);
    edits.add(edit);
    undoPosition -= (nb - 1);
  }
  
  /**
   * Called when the user tries to insert text.
   * [shift]: true if the shift key was used.
   */
  void insertNewString(String s, bool shift) {
    Position selectionStart = page.getSelectionStart();
    Position selectionEnd = page.getSelectionEnd();
    if (s == '\n' && !shift) {
      // check for newlines in lists
      if (((selectionStart.dn is DNItem) ||
          (selectionStart.dn.nextSibling == null && selectionStart.dn.parent is DNItem)) &&
          selectionStart.dnOffset == selectionStart.dn.offsetLength) {
        // \n at the end of a DNList item: adding a new list item
        DNList.newlineInItem(selectionStart);
        return;
      } else {
        DaxeNode ancestor = selectionStart.dn;
        while (ancestor is DNText || ancestor is DNHiddenP || ancestor is DNStyle || ancestor is DNStyleSpan)
          ancestor = ancestor.parent;
        if (ancestor is DNWItem) {
          // \n in a DNWList item: adding a new list item
          DNWList.newlineInItem(selectionStart);
          return;
        }
      }
    }
    // Handles automatic insertion of hidden paragraphs
    if (s == '\n' && hiddenParaRefs != null) {
      if (DNHiddenP.handleNewlineOnSelection())
        return;
    }
    // check if text is allowed here
    bool problem = false;
    DaxeNode parent = selectionStart.dn;
    if (parent.nodeType == DaxeNode.TEXT_NODE)
      parent = parent.parent;
    if (parent.userCannotEdit)
      problem = true;
    else if (s.trim() != '') {
      if (parent.nodeType == DaxeNode.DOCUMENT_NODE)
        problem = true;
      else if (parent.ref != null && !doc.cfg.canContainText(parent.ref)) {
        if (hiddenParaRefs != null) {
          x.Element hiddenp = cfg.findSubElement(parent.ref, hiddenParaRefs);
          if (hiddenp != null && selectionStart == selectionEnd) {
            // we just need to insert a hidden paragraph
            DNHiddenP p = new DNHiddenP.fromRef(hiddenp);
            p.appendChild(new DNText(s));
            insertNode(p, selectionStart);
            page.moveCursorTo(new Position(p, p.offsetLength));
            page.updateAfterPathChange();
            return;
          } else
            problem = true;
        } else
          problem = true;
      }
    }
    if (problem) {
      h.window.alert(Strings.get('insert.text_not_allowed'));
      page.cursor.clearField();
      return;
    }
    bool remove = false;
    if (selectionStart != selectionEnd) {
      selectionStart = new Position.clone(selectionStart);
      selectionEnd = new Position.clone(selectionEnd);
      page.cursor.deSelect();
      removeBetween(selectionStart, selectionEnd);
      remove = true;
      //selectionStart.dn.parent is now null if all the text inside an element was removed
      if (selectionStart.dn.parent == null)
        selectionStart = page.getSelectionStart();
    }
    doc.insertString(selectionStart, s);
    if (remove) {
      UndoableEdit edit = new UndoableEdit.compound(Strings.get('undo.insert_text'));
      edit.addSubEdit(edits.removeAt(edits.length - 2));
      edit.addSubEdit(edits.removeLast());
      edits.add(edit);
      undoPosition -= 1;
    }
  }
  
  /**
   * Creates and inserts a new Daxe node at the cursor position, displaying the attribute dialog if
   * it can have attributes.
   * The insert is added to the edits history so that it can be undone.
   */
  void insertNewNode(x.Element ref, String nodeType) {
    Position pos = page.getSelectionStart();
    if (pos == null)
      return;
    DaxeNode dn = NodeFactory.create(ref, nodeType);
    if (nodeType == 'element' && getRootElement() == null) {
      cfg.addNamespaceAttributes(dn);
    }
    dn.newNodeCreationUI(() => insert2(dn, pos));
  }
  
  /**
   * Inserts the [DaxeNode] at the given [Position] following a user action,
   * after the attribute dialog has been validated (if there are attributes).
   * If there is a selection, its contents are moved inside the new element.
   * The whole operation is added to the document history so that it can be undone.
   * Returns true if the insert worked.
   * This method is called by [insertNewNode].
   */
  bool insert2(DaxeNode dn, Position pos) {
    DaxeNode content = null;
    if (page.getSelectionStart() != page.getSelectionEnd()) {
      Position selectionStart = page.getSelectionStart();
      Position selectionEnd = page.getSelectionEnd();
      content = cloneBetween(selectionStart, selectionEnd);
      page.cursor.deSelect();
      if (pos.dn is! DNText && pos.dnOffset > 0 &&
          pos.dn.childAtOffset(pos.dnOffset - 1) is DNText) {
        // to prevent a problem if the text to the left of the selection is
        // merged with the text to the right after the removal
        // (pos would be wrong):
        DaxeNode prev = pos.dn.childAtOffset(pos.dnOffset - 1);
        pos = new Position(prev, prev.offsetLength);
      }
      removeBetween(selectionStart, selectionEnd);
      if (pos.dn.parent == null)
        pos = page.getSelectionStart();
    }
    bool inserted = false;
    DaxeNode parent = pos.dn;
    if (parent is DNText)
      parent = parent.parent;
    if (parent is DNHiddenP) {
      DNHiddenP p = parent;
      if (!cfg.isSubElement(p.ref, dn.ref)) {
        // The node must be inserted outside of the paragraph.
        // If there is something in the paragraph to the right of the cursor, it must be
        // moved into a new paragraph after the inserted node.
        UndoableEdit edit = new UndoableEdit.compound(Strings.get('undo.insert_text'));
        Position pend = new Position(p, p.offsetLength);
        pend.moveInsideTextNodeIfPossible();
        if (pos < pend) {
          DaxeNode clone = cloneBetween(pos, pend);
          edit.addSubEdit(removeBetweenEdit(pos, pend));
          DNHiddenP newp = NodeFactory.create(p.ref);
          edit.addSubEdit(new UndoableEdit.insertNode(
              new Position(p.parent, p.parent.offsetOf(p) + 1), newp));
          edit.addSubEdit(insertChildrenEdit(clone, new Position(newp, 0)));
        }
        edit.addSubEdit(new UndoableEdit.insertNode(
            new Position(p.parent, p.parent.offsetOf(p)+1), dn));
        doNewEdit(edit);
        inserted = true;
      }
    } else if (hiddenParaRefs != null && parent.ref != null && !cfg.isSubElement(parent.ref, dn.ref)) {
      x.Element hiddenp = cfg.findSubElement(parent.ref, hiddenParaRefs);
      if (hiddenp != null && cfg.isSubElement(hiddenp, dn.ref)) {
        // a new paragraph must be created
        DNHiddenP p = new DNHiddenP.fromRef(hiddenp);
        p.appendChild(dn);
        insertNode(p, pos);
        inserted = true;
      }
    }
    if (!inserted)
      insertNode(dn, pos);
    Position cursorPos = dn.firstCursorPositionInside();
    if (cursorPos == null)
      cursorPos = new Position(dn.parent, dn.parent.offsetOf(dn) + 1);
    page.moveCursorTo(cursorPos);
    page.updateAfterPathChange();
    if (content != null) {
      try {
        if (cursorPos == null)
          throw new DaxeException(content.toString() + Strings.get('insert.not_authorized_here'));
        if (doc.hiddenParaRefs != null) {
          // add or remove hidden paragraphs where necessary
          DNHiddenP.fixFragment(dn, content);
        }
        doNewEdit(insertChildrenEdit(content, cursorPos, checkValidity:true));
        // replace the 3 previous edits by a compound
        combineLastEdits(Strings.get('undo.insert_element'), 3);
        page.updateUndoMenus();
        return(true);
      } on DaxeException catch (ex) {
        h.window.alert(ex.toString());
        // inserting content didn't work: undo remove and insert
        undo();
        undo();
        edits.removeRange(edits.length - 2, edits.length);
        page.updateUndoMenus();
        return(false);
      }
    }
    return(true);
  }
  
  void executeFunction(String functionName, x.Element el) {
    // TODO: pass the parameters in el to the functions
    if (functionName == 'jaxe.FonctionNormal') {
      DNStyle.removeStylesFromSelection();
    } else if (customFunctions[functionName] != null)
      customFunctions[functionName]();
  }
  
  /**
   * Returns the list of element references which can be allowed under a given parent Daxe node.
   * For the document node, returns the list of possible root elements.
   * For a text node, returns the list of elements references allowed under its parent.
   */
  List<x.Element> elementsAllowedUnder(DaxeNode dn) {
    List<x.Element> refs;
    if (dn.nodeType == DaxeNode.DOCUMENT_NODE) {
      refs = doc.cfg.rootElements();
    } else if (dn.ref == null) {
      refs = new List<x.Element>();
    } else {
      DaxeNode parent;
      if (dn.nodeType == DaxeNode.TEXT_NODE)
        parent = dn.parent;
      else
        parent = dn;
      refs = cfg.subElements(parent.ref);
      if (parent is DNHiddenP && parent.parent.ref != null) {
        LinkedHashSet set = new LinkedHashSet.from(refs);
        set.addAll(cfg.subElements(parent.parent.ref));
        refs = new List.from(set);
      } else if (hiddenParaRefs != null) {
        x.Element hiddenp = cfg.findSubElement(parent.ref, hiddenParaRefs);
        if (hiddenp != null) {
          LinkedHashSet set = new LinkedHashSet.from(refs);
          set.addAll(cfg.subElements(hiddenp));
          refs = new List.from(set);
        }
      }
      if (parent.restrictedInserts != null) {
        for (int i=0; i<refs.length; i++) {
          if (!parent.restrictedInserts.contains(cfg.elementName(refs[i]))) {
            refs.removeAt(i);
            i--;
          }
        }
      }
    }
    return(refs);
  }
  
  /**
   * Given a list of element references, returns the elements in this list which can
   * be inserted over the current selection.
   */
  List<x.Element> validElementsInSelection(List<x.Element> allowed) {
    List<x.Element> list = new List<x.Element>();
    Position selectionStart = page.getSelectionStart();
    Position selectionEnd = page.getSelectionEnd();
    DaxeNode startParent = selectionStart.dn;
    int startOffset = selectionStart.dnOffset;
    DaxeNode endParent = selectionEnd.dn;
    int endOffset = selectionEnd.dnOffset;
    if (startParent.nodeType == DaxeNode.TEXT_NODE) {
      startOffset = startParent.parent.offsetOf(startParent);
      startParent = startParent.parent;
    }
    if (endParent.nodeType == DaxeNode.TEXT_NODE) {
      endOffset = endParent.parent.offsetOf(endParent);
      endParent = endParent.parent;
    }
    if (startParent != endParent) // this should not happen
      return(list);
    for (x.Element ref in allowed) {
      if (doc.cfg.insertIsPossible(startParent, startOffset, endOffset, ref))
        list.add(ref);
    }
    if (startParent is DNHiddenP && startParent.parent.ref != null) {
      int offset = startParent.parent.offsetOf(startParent) + 1;
      LinkedHashSet set = new LinkedHashSet.from(list);
      for (x.Element ref in allowed) {
        if (!set.contains(ref)) {
          if (doc.cfg.insertIsPossible(startParent.parent, offset, offset, ref))
            set.add(ref);
        }
      }
      list = new List.from(set);
    } else if (hiddenParaRefs != null) {
      x.Element hiddenp = null;
      for (x.Element ref in hiddenParaRefs)
        if (list.contains(ref)) {
          hiddenp = ref;
          break;
        }
      if (hiddenp != null) {
        LinkedHashSet set = new LinkedHashSet.from(list);
        for (x.Element ref in allowed) {
          if (!set.contains(ref)) {
            if (doc.cfg.isSubElement(hiddenp, ref))
              set.add(ref);
          }
        }
        list = new List.from(set);
      }
    }
    return(list);
  }
  
  /**
   * Returns the [Position] matching the given screen coordinates.
   */
  Position findPosition(num x, num y) {
    return(dndoc.findPosition(x, y));
  }
  
  /**
   * Recusively removes needless whitespaces in the element.
   */
  void removeWhitespace(final x.Element el) {
    _removeWhitespace(el, null, false, true);
  }
  
  void _removeWhitespace(final x.Element el, final x.Element refParent, final bool spacePreserveParent,
                      final bool fteParent) {
    x.Element refElement;
    if (cfg != null)
      refElement = cfg.getElementRef(el, refParent);
    else
      refElement = null;
    bool spacePreserve = _spacePreserve(el, refElement, refParent, spacePreserveParent);
    final bool fte = _isFirstTextElement(el, refElement, refParent, fteParent);
    x.Node next;
    for (x.Node n = el.firstChild; n != null; n = next) {
      next = n.nextSibling;
      if (n.nodeType == x.Node.ELEMENT_NODE)
        _removeWhitespace(n as x.Element, refElement, spacePreserve, fte);
      else if (!spacePreserve && n.nodeType == x.Node.TEXT_NODE) {
        String s = n.nodeValue;
        
        // whitespace is not removed if there is only whitespace in the element
        //if (n.nextSibling == null && n.previousSibling == null && s.trim() == "")
        //  break;
        
        if (fte && n.parentNode.firstChild == n && refParent != null) {
          // remove whitespace at the beginning if the text node is the first child of the element
          int ifin = 0;
          while (ifin < s.length && (s[ifin] == ' ' || s[ifin] == '\t'))
            ifin++;
          if (ifin > 0)
            s = s.substring(ifin);
        }
        
        // remove spaces after newlines
        int idebut = s.indexOf("\n ");
        int idebuttab = s.indexOf("\n\t");
        if (idebuttab != -1 && (idebut == -1 || idebuttab < idebut))
          idebut = idebuttab;
        while (idebut != -1) {
          int ifin = idebut;
          while (ifin + 1 < s.length && (s[ifin + 1] == ' ' || s[ifin + 1] == '\t'))
            ifin++;
          s = s.substring(0, idebut+1) + s.substring(ifin+1);
          idebut = s.indexOf("\n ");
          idebuttab = s.indexOf("\n\t");
          if (idebuttab != -1 && (idebut == -1 || idebuttab < idebut))
            idebut = idebuttab;
        }
        
        // condense spaces everywhere
        idebut = s.indexOf("  ");
        while (idebut != -1) {
          int ifin = idebut;
          while (ifin + 1 < s.length && s[ifin + 1] == ' ')
            ifin++;
          s = s.substring(0, idebut) + s.substring(ifin);
          idebut = s.indexOf("  ");
        }
        
        // update the node
        if (s.length == 0)
          el.removeChild(n);
        else
          n.nodeValue = s;
      }
    }
  }
  
  /**
   * Returns true if the text should be preserved in an element.
   */
  bool _spacePreserve(final x.Element el, final x.Element refElement, final x.Element refParent,
                      final bool spacePreserveParent) {
    bool spacePreserve;
    final String xmlspace = el.getAttribute("xml:space");
    if (xmlspace == "preserve")
      spacePreserve = true;
    else if (xmlspace == "default")
      spacePreserve = false;
    else
      spacePreserve = spacePreserveParent;
    if (refElement != null && xmlspace == "") {
      final List<x.Element> attributs = cfg.elementAttributes(refElement);
      for (x.Element attref in attributs) {
        if (cfg.attributeName(attref) == "space" &&
            cfg.attributeNamespace(attref) == "http://www.w3.org/XML/1998/namespace") {
          final String defaut = cfg.defaultAttributeValue(attref);
          if (defaut == "preserve")
            spacePreserve = true;
          else if (defaut == "default")
            spacePreserve = false;
          break;
        }
      }
    }
    return(spacePreserve);
  }
  
  /**
   * Returns false if whitespaces should not be removed at the start of the element.
   */
  bool _isFirstTextElement(final x.Element el, final x.Element refEl, final x.Element refParent,
                           final bool fteParent) {
    if (refEl == null)
      return true;
    if (refParent == null || !cfg.canContainText(refEl) || !cfg.canContainText(refParent))
      return true;
    x.Node prevNode = el.previousSibling;
    while (prevNode != null) {
      if (prevNode.nodeType == x.Node.TEXT_NODE) {
        final String prevText = prevNode.nodeValue;
        if (!(prevText.endsWith(" ") || prevText.endsWith("\n")))
          return false;
        return true;
      } else if (prevNode.nodeType == x.Node.ELEMENT_NODE) {
        final x.Node lc = prevNode.lastChild;
        if (lc != null && lc.nodeType == x.Node.TEXT_NODE) {
          final String prevText = lc.nodeValue;
          if (!(prevText.endsWith(" ") || prevText.endsWith("\n")))
            return false;
        }
        return true;
      }
      prevNode = prevNode.previousSibling;
    }
    if (prevNode == null)
      return fteParent;
    return true;
  }
  
  /**
   * Replace newlines by spaces where hidden paragraphs can be found and inside them,
   * and remove whitespace before and after blocks where hidden paragraphs can be found.
   */
  void removeWhitespaceForHiddenParagraphs(DaxeNode parent) {
    x.Element paraRef;
    if (parent.ref != null)
      paraRef = cfg.findSubElement(parent.ref, hiddenParaRefs);
    else
      paraRef = null;
    bool paraInside = (paraRef != null);
    bool para = parent is DNHiddenP;
    bool style = parent is DNStyle;
    DaxeNode next;
    for (DaxeNode dn=parent.firstChild; dn != null; dn=next) {
      next = dn.nextSibling;
      if (dn is DNText) {
        if (paraInside || para || style) {
          String s = dn.nodeValue;
          // replace newlines by spaces except between XML comments
          if (dn.previousSibling == null || dn.previousSibling is! DNComment ||
              dn.nextSibling == null || dn.nextSibling is! DNComment) {
            s = s.replaceAll('\n', ' ');
          }
          s = s.replaceAll('  ', ' ');
          if (paraInside) {
            // also trim left if there is a block before, and right if there is a block after
            // ("blocks" here are elements that are not allowed inside a paragraph)
            if (dn.previousSibling != null && dn.previousSibling.ref != null && !cfg.isSubElement(paraRef, dn.previousSibling.ref)) {
              s = s.trimLeft();
            }
            if (dn.nextSibling != null && dn.nextSibling.ref != null && !cfg.isSubElement(paraRef, dn.nextSibling.ref)) {
              s = s.trimRight();
            }
          } else if (para) {
            // trim hidden paragraphs
            if (dn.previousSibling == null)
              s = s.trimLeft();
            if (dn.nextSibling == null)
              s = s.trimRight();
          }
          if (s.length == 0)
            parent.removeChild(dn);
          else
            dn.nodeValue = s;
        }
      } else if (dn.firstChild != null)
        removeWhitespaceForHiddenParagraphs(dn);
    }
  }
  
  /**
   * Indents a DOM document recursively.
   */
  void indentDOMDocument(x.Document domdoc) {
    if (domdoc.documentElement != null)
      _indentDOMNode(domdoc.documentElement, null, false, 1);
  }
  
  /**
   * Indents a DOM node recursively.
   */
  void _indentDOMNode(final x.Element el, final x.Element refParent, final bool spacePreserveParent, final int level) {
    x.Element refElement;
    if (cfg != null)
      refElement = cfg.getElementRef(el, refParent);
    else
      refElement = null;
    bool spacePreserve = _spacePreserve(el, refElement, refParent, spacePreserveParent);
    for (x.Node n = el.firstChild; n != null; n = n.nextSibling) {
      if (n.nodeType == x.Node.ELEMENT_NODE)
        _indentDOMNode(n as x.Element, refElement, spacePreserve, level+1);
      else if (!spacePreserve && n.nodeType == x.Node.TEXT_NODE && n.nodeValue.contains("\n")) {
        StringBuffer sb = new StringBuffer("\n");
        int indents = level;
        if (n.nextSibling == null)
          indents--;
        for (int i=0; i<indents; i++)
          sb.write('  ');
        String newline_spaces = sb.toString();
        n.nodeValue = n.nodeValue.replaceAll("\n", newline_spaces);
      }
    }
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/daxe_exception.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/daxe_exception.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

class DaxeException implements Exception {
  final String message;
  final Exception parentException;
  
  const DaxeException([this.message, this.parentException]);
  
  String toString() {
    String s;
    if (message == null)
      s = 'DaxeException';
    else
      s = message;
    if (parentException != null)
      s = "$s (parent exception: $parentException)";
    return(s);
  }
  
}


Index: modules/damieng/graphical_editor/daxe/lib/src/daxe_node.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/daxe_node.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

/**
 * This class represents a GUI for an XML node. The subclasses offer differents GUIs.
 */
abstract class DaxeNode {
  static const int ELEMENT_NODE = 1;
  static const int TEXT_NODE = 3;
  //NOTE: cdata, pi and comments are now DaxeNode elements containing a text node
  //static const int CDATA_SECTION_NODE = 4;
  //static const int PROCESSING_INSTRUCTION_NODE = 7;
  //static const int COMMENT_NODE = 8;
  static const int DOCUMENT_NODE = 9;
  
  static const String STYLE_BOLD = 'GRAS';
  static const String STYLE_ITALIC = 'ITALIQUE';
  static const String STYLE_SUPERSCRIPT = 'EXPOSANT';
  static const String STYLE_SUBSCRIPT = 'INDICE';
  static const String STYLE_UNDERLINE = 'SOULIGNE';
  static const String STYLE_STRIKETHROUGH = 'BARRE';
  static const String STYLE_BACKGROUND_COLOR = 'FCOULEUR';
  static const String STYLE_FOREGROUND_COLOR = 'PCOULEUR';
  static const String COLOR_PATTERN = "^.*\\[(x[0-9a-fA-F]{2}|[0-9]{1,3}),(x[0-9a-fA-F]{2}|[0-9]{1,3}),(x[0-9a-fA-F]{2}|[0-9]{1,3})\\]\$";

  x.Element ref; // schema element
  String _id;
  DaxeNode parent;
  int nodeType;
  String _namespaceURI;
  String prefix;
  String localName;
  String nodeValue;
  DaxeNode firstChild;
  DaxeNode nextSibling;
  List<DaxeAttr> attributes;
  bool userCannotRemove = false; // with suppr/del, could be extended to selections...
  bool userCannotEdit = false;
  bool valid;
  List<String> restrictedInserts; // used in DaxeDocument.elementsAllowedUnder to restrict inserts beyond schema
  
  
  /**
   * Constructor using a DOM [node] and a DaxeNode [parent].
   * Will create children nodes recursively unless [createChildren] is false.
   */
  DaxeNode.fromNode(x.Node node, DaxeNode parent, {bool createChildren: true}) {
    _id = doc.newId(this);
    this.parent = parent;
    if (node.nodeType == x.Node.ELEMENT_NODE || node.nodeType == x.Node.TEXT_NODE ||
        node.nodeType == x.Node.DOCUMENT_NODE)
      nodeType = node.nodeType;
    else
      nodeType = ELEMENT_NODE;
    _namespaceURI = node.namespaceURI;
    prefix = node.prefix;
    if (node.nodeType == x.Node.PROCESSING_INSTRUCTION_NODE)
      localName = node.nodeName;
    else if (node.nodeType == x.Node.CDATA_SECTION_NODE)
      localName = '#cdata-section';
    else if (node.nodeType == x.Node.COMMENT_NODE)
      localName = '#comment';
    else if (node.nodeType == x.Node.DOCUMENT_NODE)
      localName = '#document';
    else
      localName = node.localName;
    if (nodeType == DaxeNode.TEXT_NODE)
      nodeValue = node.nodeValue;
    attributes = new List<DaxeAttr>();
    LinkedHashMap<String, x.Attr> nm = node.attributes;
    if (nm != null) {
      for (x.Node n in nm.values) {
        attributes.add(new DaxeAttr.fromNode(n));
      }
    }
    if (node is x.Element) {
      ref = doc.cfg.getElementRef(node, parent == null ? null : parent.ref);
      if (ref == null && parent != null) {
        // could not find a reference when taking the parent into account
        // there is probably an error in the document, but we will try to use
        // another reference by ignoring the parent
        ref = doc.cfg.elementReference(localName);
      }
    }
    
    if (createChildren) {
      if (node.childNodes != null) {
        DaxeNode prev = null;
        for (x.Node n in node.childNodes) {
          DaxeNode dn = NodeFactory.createFromNode(n, this);
          if (prev == null)
            firstChild = dn;
          else
            prev.nextSibling = dn;
          prev = dn;
        }
      } else if ((node.nodeType == x.Node.CDATA_SECTION_NODE ||
          node.nodeType == x.Node.PROCESSING_INSTRUCTION_NODE ||
          node.nodeType == x.Node.COMMENT_NODE) &&
          node.nodeValue != null && node.nodeValue != '') {
        appendChild(new DNText(node.nodeValue));
      }
    }
    if (nodeType == DaxeNode.ELEMENT_NODE)
      valid = doc.cfg.elementIsValid(this);
    else
      valid = true;
  }
  
  /**
   * Constructor using an element reference.
   * This will always create an element node.
   */
  DaxeNode.fromRef(x.Element elementRef) {
    ref = elementRef;
    _id = doc.newId(this);
    parent = null;
    nodeType = ELEMENT_NODE;
    _namespaceURI = doc.cfg.elementNamespace(ref);
    prefix = doc.cfg.elementPrefix(ref);
    localName = doc.cfg.elementName(ref);
    nodeValue = null;
    firstChild = null;
    nextSibling = null;
    attributes = new List<DaxeAttr>();
    valid = true;
  }
  
  /**
   * Constructor using a node type.
   * Useful to create new document, cdata, pi or comment nodes (they don't have a ref).
   * Possible node types are available as constants of this class.
   */
  DaxeNode.fromNodeType(int nodeType) {
    ref = null;
    _id = doc.newId(this);
    parent = null;
    this.nodeType = nodeType;
    _namespaceURI = null;
    prefix = null;
    localName = null;
    nodeValue = null;
    firstChild = null;
    nextSibling = null;
    attributes = new List<DaxeAttr>();
    valid = true;
  }
  
  /**
   * Constructor for a text node with the given [value].
   */
  DaxeNode.text(String value) {
    _id = doc.newId(this);
    parent = null;
    nodeType = DaxeNode.TEXT_NODE;
    _namespaceURI = null;
    this.prefix = null;
    this.localName = null;
    nodeValue = value;
    firstChild = null;
    nextSibling = null;
    attributes = null;
    valid = true;
  }
  
  /**
   * Deep clone constructor, using the DOM serialization.
   */
  factory DaxeNode.clone(DaxeNode dn) {
    x.DOMImplementation domimpl = new x.DOMImplementationImpl();
    x.Document domdoc = domimpl.createDocument(null, null, null);
    x.Node n = dn.toDOMNode(domdoc);
    DaxeNode clone = NodeFactory.createFromNode(n, dn.parent);
    clone.parent = null;
    return(clone);
  }
  
  
  /**
   * Id for the corresponding HTML element.
   */
  String get id {
    return(_id);
  }
  
  /**
   * Returns the corresponding HTML element.
   */
  h.Element getHTMLNode() {
    return(h.document.getElementById(_id));
  }
  
  /**
   * Returns the HTML element in which the contents of the XML element will be displayed.
   * This is used in Position to find where to display the cursor when there is no child.
   */
  h.Element getHTMLContentsNode() {
    h.Element hn = getHTMLNode();
    if (hn != null && !hn.nodes.isEmpty && hn.firstChild is h.Element)
      hn = hn.firstChild;
    return(hn);
  }
  
  String get nodeName {
    if (nodeType == TEXT_NODE)
      return('#text');
    StringBuffer buff = new StringBuffer();
    if (prefix != null) {
      buff.write(prefix);
      buff.write(":");
    }
    buff.write(localName);
    return(buff.toString());
  }
  
  String get namespaceURI {
    return(_namespaceURI);
  }
  
  /**
   * For a text node, the number of characters in the node value.
   * Otherwise, the number of children.
   */
  int get offsetLength {
    if (nodeType == TEXT_NODE)
      return(nodeValue.length);
    int n = 0;
    for (DaxeNode dn=firstChild; dn != null; dn=dn.nextSibling)
      n++;
    return(n);
  }
  
  /**
   * Returns true if this node does not have delimiters such as tags or a box.
   * This affects cursor behavior (for instance, a backspace after a tag removes the node,
   * and 2 blocks with the same ref can be merged when a backspace is used
   * at the beginning of the second block)
   */
  bool get noDelimiter {
    return(false);
  }
  
  /**
   * Returns true if this node is displayed like a block
   * (such as area, division or hiddenp, with line breaks).
   */
  bool get block {
    // this is only a guess, it should be subclassed to be safe
    if (newlineAfter())
      return(true);
    h.Element hnode = getHTMLNode(); 
    return(hnode is h.DivElement || hnode is h.TableElement || hnode is h.UListElement);
  }
  
  /**
   * The child nodes in a convenient list.
   */
  List<DaxeNode> get childNodes {
    List<DaxeNode> list = new List<DaxeNode>();
    for (DaxeNode dn=firstChild; dn != null; dn=dn.nextSibling) {
      list.add(dn);
    }
    return(list);
  }
  
  /**
   * This node's previous sibling.
   */
  DaxeNode get previousSibling {
    if (parent == null)
      return(null);
    for (DaxeNode dn = parent.firstChild; dn != null; dn = dn.nextSibling) {
      if (dn.nextSibling == this)
        return(dn);
    }
    return(null);
  }
  
  DaxeNode get lastChild {
    for (DaxeNode dn = firstChild; dn != null; dn = dn.nextSibling) {
      if (dn.nextSibling == null)
        return(dn);
    }
    return(null);
  }
  
  DaxeNode childAtOffset(int offset) {
    assert(nodeType != TEXT_NODE);
    int n = 0;
    for (DaxeNode dn=firstChild; dn != null; dn=dn.nextSibling) {
      if (n == offset)
        return(dn);
      n++;
    }
    return(null);
  }
  
  /**
   * Returns the next node in the document (excluding attribute nodes).
   */
  DaxeNode nextNode() {
    if (firstChild != null)
      return(firstChild);
    if (nextSibling != null)
      return(nextSibling);
    DaxeNode p = parent;
    while (p != null) {
      if (p.nextSibling != null)
        return(p.nextSibling);
      p = p.parent;
    }
    return(null);
  }
  
  /**
   * Returns the previous node in the document (excluding attribute nodes).
   */
  DaxeNode previousNode() {
    if (firstChild != null)
      return(lastChild);
    if (previousSibling != null)
      return(previousSibling);
    DaxeNode p = parent;
    while (p != null) {
      if (p.previousSibling != null)
        return(p.previousSibling);
      p = p.parent;
    }
    return(null);
  }
  
  /**
   * Returns the index of the given child node.
   */
  int offsetOf(DaxeNode child) {
    int i = 0;
    for (DaxeNode n=firstChild; n != null; n=n.nextSibling) {
      if (n == child)
        return(i);
      i++;
    }
    assert(false);
    return(-1);
  }
  
  String getAttribute(String name) {
    for (DaxeAttr att in attributes) {
      if (att.localName == name)
        return(att.value);
    }
    return(null);
  }
  
  String getAttributeNS(String namespaceURI, String localName) {
    if (attributes == null)
      return(null);
    for (DaxeAttr att in attributes) {
      if (att.namespaceURI == namespaceURI && att.localName == localName)
        return(att.value);
    }
    return(null);
  }
  
  void setAttribute(String name, String value) {
    for (DaxeAttr att in attributes) {
      if (att.localName == name) {
        att.value = value;
        return;
      }
    }
    attributes.add(new DaxeAttr(name, value));
    return;
  }
  
  void removeAttribute(String name) {
    for (DaxeAttr att in attributes) {
      if (att.localName == name) {
        attributes.remove(att);
        return;
      }
    }
  }
  
  void setAttributeNS(String namespaceURI, String qualifiedName, String value) {
    String attPrefix, attLocalName;
    int ind = qualifiedName.indexOf(":");
    if (ind != -1) {
      attPrefix = qualifiedName.substring(0, ind);
      attLocalName = qualifiedName.substring(ind+1);
    } else {
      attPrefix = null;
      attLocalName = qualifiedName;
    }
    DaxeAttr att = getAttributeNodeNS(namespaceURI, attLocalName);
    if (att != null) {
      att.prefix = attPrefix;
      att.value = value;
      return;
    }
    att = new DaxeAttr.NS(namespaceURI, qualifiedName, value);
    attributes.add(att);
  }
  
  DaxeAttr getAttributeNode(String name) {
    for (DaxeAttr att in attributes) {
      if (att.localName == name)
        return(att);
    }
    return(null);
  }
  
  DaxeAttr getAttributeNodeNS(String namespaceURI, String localName) {
    if (attributes == null)
      return(null);
    for (DaxeAttr att in attributes) {
      if (att.namespaceURI == namespaceURI && att.localName == localName) {
        return(att);
      }
    }
    return(null);
  }
  
  LinkedHashMap<String, DaxeAttr> getAttributesMapCopy() {
    LinkedHashMap<String, DaxeAttr> map = new LinkedHashMap<String, DaxeAttr>();
    for (DaxeAttr attr in attributes)
      map[attr.name] = new DaxeAttr.clone(attr);
    return(map);
  }
  
  /**
   * Creates and returns the HTML element for this DaxeNode.
   * This abstract method must be overriden by subclasses.
   */
  h.Element html();
  
  /**
   * Update the display. By default, this recreates all the HTML.
   */
  void updateHTML() {
    h.Element vel = getHTMLNode();
    if (vel == null)
      return;
    h.Element nel = html();
    vel.replaceWith(nel);
  }
  
  /**
   * Update the display after children changed (insert/removal/other).
   * This method can be overriden for optimization.
   */
  void updateHTMLAfterChildrenChange(List<DaxeNode> changed) {
    List<DaxeNode> children = childNodes;
    for (DaxeNode child in changed) {
      h.Element hn = child.getHTMLNode();
      if (!children.contains(child)) {
        // removal
        if (hn != null)
          hn.remove();
        else {
          // no reference to the HTML node, only the derived class
          // could know how to remove the child display
          updateHTML();
          return;
        }
      } else if (hn == null) {
        // insert
        DaxeNode next = child.nextSibling;
        h.Node nextHn = null;
        while (nextHn == null && next != null) {
          nextHn = next.getHTMLNode();
          if (nextHn == null)
            next = next.nextSibling;
        }
        DaxeNode prev = child.previousSibling;
        h.Node prevHn = null;
        while (prevHn == null && prev != null) {
          prevHn = prev.getHTMLNode();
          if (prevHn == null)
            prev = prev.previousSibling;
        }
        if (nextHn != null) {
          nextHn.parent.insertBefore(child.html(), nextHn);
        } else if (prevHn != null) {
          // there might be some nodes at the end of the parent that we want
          // to keep at the end (for instance the space at the end of a cell)
          if (prevHn.nextNode != null)
            prevHn.parent.insertBefore(child.html(), prevHn.nextNode);
          else
            prevHn.parent.append(child.html());
        } else {
          // no sibling, there might not even be a content div...
          //TODO getHTMLContentsNode
          updateHTML();
          return;
        }
      } else {
        // change
        child.updateHTML();
      }
    }
  }
  
  /**
   * The attributes have changed, an update might be needed.
   * This method can be overriden for optimization.
   */
  void updateAttributes() {
    updateHTML();
  }
  
  /**
   * Sets whether this node is selected by the user or not.
   */
  void setSelected(bool select) {
    h.Element hn = getHTMLNode();
    if (hn == null)
      return;
    if (select)
      hn.classes.add('selected');
    else
      hn.classes.remove('selected');
  }
  
  void appendChild(DaxeNode dn) {
    DaxeNode last = lastChild;
    if (last != null)
      last.nextSibling = dn;
    else
      firstChild = dn;
    dn.parent = this;
  }
  
  /**
   * Inserts [newdn] as a child of this node before [beforedn].
   * beforedn may be null, in which case it is inserted as the last child.
   */
  void insertBefore(DaxeNode newdn, DaxeNode beforedn) {
    assert(beforedn == null || this == beforedn.parent);
    newdn.parent = this;
    DaxeNode dn = firstChild;
    if (dn == beforedn) {
      DaxeNode save = firstChild;
      firstChild = newdn;
      newdn.nextSibling = save;
    } else {
      while (dn != null && dn.nextSibling != beforedn) {
        dn = dn.nextSibling;
      }
      assert(dn != null);
      assert(dn.nextSibling == beforedn);
      DaxeNode save = dn.nextSibling;
      dn.nextSibling = newdn;
      newdn.nextSibling = save;
    }
  }
  
  void insertAfter(DaxeNode newdn, DaxeNode afterdn) {
    assert(this == afterdn.parent);
    if (afterdn.nextSibling == null)
      appendChild(newdn);
    else
      insertBefore(newdn, afterdn.nextSibling);
  }
  
  void removeChild(DaxeNode dn) {
    if (dn.previousSibling != null)
      dn.previousSibling.nextSibling = dn.nextSibling;
    if (dn == firstChild)
      firstChild = dn.nextSibling;
    dn.parent = null;
    dn.nextSibling = null;
  }
  
  /**
   * Replaces this node by the given node (in the tree).
   */
  void replaceWith(DaxeNode dn) {
    if (parent.firstChild == this)
      parent.firstChild = dn;
    else
      previousSibling.nextSibling = dn;
    dn.parent = parent;
    dn.nextSibling = nextSibling;
    parent = null;
    nextSibling = null;
  }
  
  /**
   * Merges adjacent child text nodes.
   */
  void normalize() {
    for (DaxeNode dn=firstChild; dn != null; dn=dn.nextSibling) {
      while (dn.nodeType == TEXT_NODE && dn.nextSibling != null &&
          dn.nextSibling.nodeType == TEXT_NODE) {
        dn.nodeValue = "${dn.nodeValue}${dn.nextSibling.nodeValue}";
        removeChild(dn.nextSibling);
      }
    }
  }
  
  void remove(Position pos, int length) {
    if (nodeType == ELEMENT_NODE) {
      for (int i=pos.dnOffset; i<length; i++) {
        removeChild(childAtOffset(pos.dnOffset));
      }
    } else {
      String v = nodeValue;
      String s1 = v.substring(0, pos.dnOffset);
      String s2 = v.substring(pos.dnOffset + length);
      nodeValue = "$s1$s2";
    }
  }
  
  /**
   * Add a newline after this element when serializing.
   * If it returns true, fixLineBreaks() is usually called in the fromNode constructor.
   */
  bool newlineAfter() {
    return(false);
  }
  
  /**
   * Add a newline after the start tag and if necessary a newline before the end tag
   * in this element when serializing.
   * If it returns true, fixLineBreaks() is usually called in the fromNode constructor.
   */
  bool newlineInside() {
    return(false);
  }
  
  /**
   * Remove newlines that will be added at serialization.
   */
  void fixLineBreaks() {
    if (newlineInside() && firstChild != null && firstChild is DNText) {
      String s = firstChild.nodeValue;
      if (s.startsWith('\n')) {
        if (s.length == 1)
          removeChild(firstChild);
        else
          firstChild.nodeValue = s.substring(1);
      }
    }
    DaxeNode lastNotText = lastChild;
    while (lastNotText != null && lastNotText is DNText)
      lastNotText = lastNotText.previousSibling;
    if (newlineInside() && lastChild is DNText && (lastNotText == null || !lastNotText.newlineAfter())) {
      String s = lastChild.nodeValue;
      if (s.endsWith('\n')) {
        if (s.length == 1)
          removeChild(lastChild);
        else
          lastChild.nodeValue = s.substring(0, s.length - 1);
      }
    }
    for (DaxeNode dn=firstChild; dn != null; dn=dn.nextSibling) {
      if (dn.newlineAfter() && dn.nextSibling is DNText) {
        String s = dn.nextSibling.nodeValue;
        if (s.startsWith('\n')) {
          if (s.length == 1)
            removeChild(dn.nextSibling);
          else
            dn.nextSibling.nodeValue = s.substring(1);
        }
      }
    }
  }
  
  /**
   * DOM serialization. Can be overriden in DaxeNode subclasses.
   */
  x.Node toDOMNode(x.Document domDocument) {
    assert(nodeType == ELEMENT_NODE); // the other types are handled in subclasses DNDocument and DNText
    x.Element el = domDocument.createElementNS(namespaceURI, nodeName);
    for (DaxeAttr att in attributes)
      el.setAttributeNS(att.namespaceURI, att.name, att.value);
    if (newlineInside() || firstChild != null) {
      if (newlineInside())
        el.appendChild(domDocument.createTextNode('\n'));
      for (DaxeNode dn=firstChild; dn != null; dn=dn.nextSibling) {
        el.appendChild(dn.toDOMNode(domDocument));
        if (dn.newlineAfter())
          el.appendChild(domDocument.createTextNode('\n'));
      }
      DaxeNode lastNotText = lastChild;
      while (lastNotText != null && lastNotText is DNText)
        lastNotText = lastNotText.previousSibling;
      if (newlineInside() && lastChild != null && (lastNotText == null || !lastNotText.newlineAfter()))
        el.appendChild(domDocument.createTextNode('\n'));
    }
    return(el);
  }
  
  /**
   * XML serialization. Based on DOM serialization (see [toDOMNode]);
   */
  String toString() {
    x.DOMImplementation domimpl = new x.DOMImplementationImpl();
    x.Document domdoc = domimpl.createDocument(null, null, null);
    x.Node n = toDOMNode(domdoc);
    return(n.toString());
  }
  
  /// escapes XML character entities for serialization
  static String escape(String s) {
    s = s.replaceAll('&', '&');
    s = s.replaceAll('"', '"');
    //s = s.replaceAll("'", ''');
    s = s.replaceAll('<', '<');
    s = s.replaceAll('>', '>');
    return(s);
  }
  
  void updateValidity() {
    valid = doc.cfg.elementIsValid(this);
    h.Element hel = getHTMLNode();
    if (hel == null)
      return;
    if (valid && hel.classes.contains('invalid'))
      hel.classes.remove('invalid');
    else if (!valid && !hel.classes.contains('invalid'))
      hel.classes.add('invalid');
  }
  
  /**
   * This method is called when the user creates a new node, before it is inserted.
   * By default, it displays the attribute dialog when there are attributes.
   */
  void newNodeCreationUI(ActionFunction okfct) {
    if (ref != null && doc.cfg.elementAttributes(ref).length > 0)
      attributeDialog(() => okfct());
    else
      okfct();
  }
  
  void attributeDialog([ActionFunction okfct]) {
    if (ref != null) {
      AttributeDialog dlg = new AttributeDialog(this, okfct);
      dlg.show();
    } else {
      UnknownElementDialog dlg = new UnknownElementDialog(this, okfct);
      dlg.show();
    }
  }
  
  Position findPosition(num x, num y) {
    // we assume the click was in this element
    
    /*
     I wish I could use Range, like this:
     if (document.caretRangeFromPoint) {
       range = document.caretRangeFromPoint(e.pageX, e.pageY);
     } else if (e.rangeParent) {
       range = document.createRange();
       range.setStart(e.rangeParent, e.rangeOffset);
     }
     but caretRangeFromPoint does not work in Firefox, and rangeParent/rangeOffset
     do not exist in Dart...
     cf http://stackoverflow.com/q/3189812/438970
     and http://code.google.com/p/dart/issues/detail?id=9227
     and http://code.google.com/p/dart/issues/detail?id=11723
     */
    Position pos = new Position(this, 0);
    
    if (nodeType == ELEMENT_NODE || nodeType == DOCUMENT_NODE) {
      for (DaxeNode dn=firstChild; dn != null; dn=dn.nextSibling) {
        // hnx1, hny1, lineHeight: first char in HTML element
        // hnx2, hny2, lineHeight: last char
        h.Element hn = dn.getHTMLNode();
        if (hn == null)
          continue;
        double hnx1, hny1, hnx2, hny2;
        double topLineHeight, bottomLineHeight;
        // NOTE: the main problem here is to avoid adding spans to find the position
        if (hn is h.DivElement && hn.nodes.length > 0 &&
            hn.firstChild is h.SpanElement && (hn.firstChild as h.SpanElement).classes.contains('start_tag') &&
            hn.lastChild is h.SpanElement&& (hn.lastChild as h.SpanElement).classes.contains('end_tag')) {
          // the spans are tags
          h.Element span_test = hn.firstChild;
          h.Rectangle box = span_test.getBoundingClientRect();
          hnx1 = box.left;
          hny1 = box.top;
          topLineHeight = span_test.offset.height.toDouble();
          span_test = hn.lastChild;
          box = span_test.getBoundingClientRect();
          hnx2 = box.right;
          hny2 = box.bottom;
          bottomLineHeight = span_test.offset.height.toDouble();
        } else if (hn is h.DivElement || hn is h.TableCellElement || hn is h.TableRowElement ||
            hn is h.TableElement || hn is h.ImageElement || hn.classes.contains('form')) {
          // block
          // for DivElement: no span to tag the div: we take the entire div into account
          h.Rectangle box = hn.getBoundingClientRect();
          // FIXME: box is not good for a tr containing a td using rowspan
          hnx1 = box.left;
          hny1 = box.top;
          if (hn.classes.contains('form')) // FIXME: this is a hack !
            hnx2 = hn.querySelector('table').getBoundingClientRect().right;
          else
            hnx2 = box.right;
          hny2 = box.bottom;
          topLineHeight = bottomLineHeight = box.height;
        } else if (hn is h.SpanElement && hn.nodes.length == 1 && hn.firstChild is h.Text &&
            !hn.firstChild.nodeValue.endsWith('\n')) {
          // text node that does not end with \n
          List<h.Rectangle> rects = hn.getClientRects();
          if (rects.length == 0)
            return(null);
          h.Rectangle box = rects.first;
          hnx1 = box.left;
          hny1 = box.top;
          topLineHeight = box.height * 1.4;
          box = rects.last;
          hnx2 = box.right;
          hny2 = box.bottom;
          bottomLineHeight = box.height * 1.4;
        } else if (hn.firstChild is h.Element && hn.lastChild is h.SpanElement &&
            hn.lastChild.lastChild is h.Text &&
            !hn.lastChild.lastChild.nodeValue.endsWith('\n')) {
          // span with a text node at the end which does not end with \n
          // note: possibles selections on the text make the tests a bit complex...
          h.Element span_test = hn.firstChild;
          List<h.Rectangle> rects = span_test.getClientRects();
          if (rects.length == 0)
            return(null);
          h.Rectangle box = rects.first;
          hnx1 = box.left;
          hny1 = box.top;
          topLineHeight = box.height * 1.3;
          span_test = hn.lastChild;
          rects = span_test.getClientRects();
          if (rects.length == 0)
            return(null);
          box = rects.last;
          hnx2 = box.right;
          hny2 = box.bottom;
          bottomLineHeight = box.height * 1.3;
        } else {
          // NOTE: for a span, getBoundingClientRect and getClientRects return a wrong bottom when
          // there are \n inside (except maybe with IE), so we can't even use them on hn to avoid
          // appending spans (and bec. it depends on browsers we can't just add a \n).
          // Using an empty span does not work.
          // FIXME: adding a span with text can change a table layout with Firefox,
          // causing wrong results and side effects
          // -> TODO: test WORD JOINER ("\u2060") instead of "|"
          h.Element span_test = new h.Element.tag('span');
          span_test.append(new h.Text("|"));
          if (hn.nodes.length == 1 && hn.firstChild is h.BRElement) {
            hn.append(span_test);
            h.Rectangle box = span_test.getBoundingClientRect();
            hnx1 = -1.0;
            hny1 = box.top;
            topLineHeight = bottomLineHeight = span_test.offset.height.toDouble() * 1.4;
            hnx2 = -1.0;
            hny2 = box.bottom;
            span_test.remove();
          } else {
            if (hn.nodes.isEmpty)
              hn.append(span_test);
            else
              hn.insertBefore(span_test, hn.firstChild);
            h.Rectangle box = span_test.getBoundingClientRect();
            hnx1 = box.left;
            hny1 = box.top;
            // note: maybe we should use CSS line-height here, but it is hard to get the value
            topLineHeight = span_test.offset.height.toDouble() * 1.4;
            span_test.remove();
            if (hn is h.LIElement) {
              h.Node lastDescendant = hn;
              while (lastDescendant.firstChild != null &&
                  (lastDescendant.lastChild is! h.Text ||
                      (lastDescendant.lastChild.nodeValue == '\n' && lastDescendant.lastChild.previousNode != null)) &&
                  lastDescendant.lastChild is! h.ImageElement) {
                if (lastDescendant.lastChild is h.Text && lastDescendant.lastChild.nodeValue == '\n' &&
                    lastDescendant.lastChild.previousNode != null)
                  lastDescendant = lastDescendant.lastChild.previousNode; // case of a hidden paragraph or block inside li
                else
                  lastDescendant = lastDescendant.lastChild;
              }
              lastDescendant.append(span_test);
            } else
              hn.append(span_test);
            box = span_test.getBoundingClientRect();
            hnx2 = box.left;
            hny2 = box.bottom;
            bottomLineHeight = span_test.offset.height.toDouble() * 1.4;
            span_test.remove();
          }
        }
        if ((y < hny1 + topLineHeight && (y < hny1 - 1 || (x < hnx1 + 1 && hn is! h.LIElement &&
            dn is! DNHiddenP)))) {
          // position is before this child
          return(pos);
        }
        if (y > hny2 - bottomLineHeight && (y > hny2 + 1 || (x > hnx2 - 1 && hn is! h.LIElement))) {
          // position is after this child
          pos = new Position(this, offsetOf(dn) + 1);
        } else {
          // position is within this child
          pos = dn.findPosition(x, y);
          return(pos);
        }
      }
    } else if (nodeType == TEXT_NODE) {
      /* doesn't work with other browsers than Chrome...
      h.Range range = h.document.$dom_caretRangeFromPoint(x, y);
      h.Element hn = getHTMLNode();
      if (range.startContainer == hn || range.startContainer == hn.$dom_firstChild)
        return(new Position(this, range.startOffset));
      */
      int pp = 0; // position in the XML node
      for (h.Node hn in getHTMLNode().nodes) {
        h.Text ht;
        if (hn is h.Text)
          ht = hn;
        else if (hn is h.Element) // selection span
          ht = hn.firstChild;
        else
          continue;
        h.Range range = new h.Range();
        for (int i=0; i<ht.length; i++) {
          // problem: depending on the browser, the clientrects are not the same
          // for \n or for line-breaking space characters (or for the character before a \n with IE11)...
          if (nodeValue[pp + i] != '\n') {
            range.setStart(ht, i);
            range.setEnd(ht, i+1);
            List<h.Rectangle> rects = range.getClientRects();
            for (h.Rectangle r in rects) {
              if (h.window.navigator.appVersion.contains("Trident") && pp + i + 1 < nodeValue.length &&
                  nodeValue[pp + i + 1] == '\n' && r.width == 0) {
                // With IE11, ignore a 0-width rect for the character before a \n
                continue;
              }
              if (i < nodeValue.length - 1 && r.left == r.right &&
                  x < r.left && y < r.bottom) {
                //print("left of line start after newline");
                return(new Position(this, pp + i+1));
              } if (x < r.right && y <= r.bottom) {
                if (x < (r.left + r.right) / 2)
                  return(new Position(this, pp + i));
                else
                  return(new Position(this, pp + i+1));
              } else if (y < r.top - 5) {
                //print("line above");
                // the point is on the line above
                if (pp+i == 0 || nodeValue[pp+i] == ' ')
                  return(new Position(this, pp + i));
                else
                  return(new Position(this, pp + i - 1));
              }
            }
          } else {
            // ranges are not reliable for positions of newline characters
            // FIXME: adding text can change a table layout with Firefox, causing wrong results
            String s = ht.text;
            ht.text = "${s.substring(0, i)}|${s.substring(i)}";
            range.setStart(ht, i);
            range.setEnd(ht, i+1);
            List<h.Rectangle> rects = range.getClientRects();
            ht.text = s;
            if (h.window.navigator.appVersion.contains("Trident")) {
              // tested on IE11
              if (y <= rects.first.bottom) {
                // before the line bottom
                return(new Position(this, pp + i));
              }
            } else {
              // tested on Chromium and Firefox
              for (h.Rectangle r in rects) {
                if (y <= r.bottom) {
                  // before the line bottom
                  return(new Position(this, pp + i));
                }
              }
            }
          }
        }
        pp += ht.length;
      }
      // not found...
      //print("position not found");
    }
    return(new Position(this, offsetLength));
  }
  
  Position firstCursorPositionInside() {
    return(new Position(this, 0));
  }
  
  Position lastCursorPositionInside() {
    return(new Position(this, offsetLength));
  }
  
  void setStyle(h.Element hn) {
    String styleParam = doc.cfg.elementParameterValue(ref, 'style', null);
    if (styleParam != null) {
      List<String> styleList = styleParam.split(';');
      for (String style in styleList) {
        if (style == STYLE_BOLD) {
          hn.style.fontWeight = 'bold';
        } else if (style == STYLE_ITALIC) {
          hn.style.fontStyle = 'italic';
        } else if (style == STYLE_SUPERSCRIPT) {
          hn.style.verticalAlign = 'super';
          hn.style.fontSize = '80%';
        } else if (style == STYLE_SUBSCRIPT) {
          hn.style.verticalAlign = 'sub';
          hn.style.fontSize = '80%';
        } else if (style == STYLE_UNDERLINE) {
          hn.style.textDecoration = 'underline';
        } else if (style == STYLE_STRIKETHROUGH) {
          hn.style.textDecoration = 'line-through';
        } else if (style.startsWith(STYLE_BACKGROUND_COLOR)) {
          hn.style.background = _getColor(style);
        } else if (style.startsWith(STYLE_FOREGROUND_COLOR)) {
          hn.style.color = _getColor(style);
        }
      }
    }
    String fontParam = doc.cfg.elementParameterValue(ref, 'police', null);
    if (fontParam != null) {
      if (fontParam == 'Monospaced')
        fontParam = 'monospace';
      hn.style.fontFamily = fontParam;
    }
    String sizeParam = doc.cfg.elementParameterValue(ref, 'taille', null);
    if (sizeParam != null) {
      hn.style.fontSize = sizeParam;
    }
  }
  
  String _getColor(String style) {
    Iterable<Match> matches = COLOR_PATTERN.allMatches(style);
    for (Match m in matches) {
      final List<int> color = new List<int>();
      for (int j = 0; j < 3; j++) {
        String value = m.group(j + 1);
        if (value.startsWith("x"))
          color[j] = int.parse(value.substring(1), radix: 16);
        else
          color[j] = int.parse(value);
      }
      return("rgb(${color[0]}, ${color[1]}, ${color[2]})");
    }
    return(null);
  }
  
  /**
   * Calls afterInsert for this node and all its descendants.
   */
  void callAfterInsert() {
    afterInsert();
    for (DaxeNode dn=firstChild; dn != null; dn=dn.nextSibling) {
      dn.callAfterInsert();
    }
  }
  
  /**
   * Calls beforeRemove for this node and all its descendants.
   */
  void callBeforeRemove() {
    beforeRemove();
    for (DaxeNode dn=firstChild; dn != null; dn=dn.nextSibling) {
      dn.callBeforeRemove();
    }
  }
  
  /**
   * Called after this node was inserted. Does nothing by default.
   */
  void afterInsert() {
  }
  
  /**
   * Called before this node is removed. Does nothing by default.
   */
  void beforeRemove() {
  }
  
  /**
   * Returns true if the children should be using ParentUpdatingDNText instead of DNText
   */
  bool get needsParentUpdatingDNText {
    return false;
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/file_open_dialog.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/file_open_dialog.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

/**
 * This class is useless, it does not return a real path on the disk.
 */
@deprecated
class FileOpenDialog {
  ActionFunction okfct;
  h.File file;
  
  FileOpenDialog(this.okfct) {
  }
  
  void show() {
    h.DivElement div1 = new h.DivElement();
    div1.id = 'dlg1';
    div1.classes.add('dlg1');
    h.DivElement div2 = new h.DivElement();
    div2.classes.add('dlg2');
    h.DivElement div3 = new h.DivElement();
    div3.classes.add('dlg3');
    h.FormElement form = new h.FormElement();
    
    h.FileUploadInputElement input = new h.FileUploadInputElement();
    input.id = 'file_input';
    input.onChange.listen((h.Event event) => onChange(event));
    form.append(input);
    
    h.DivElement div_buttons = new h.DivElement();
    div_buttons.classes.add('buttons');
    h.ButtonElement bCancel = new h.ButtonElement();
    bCancel.attributes['type'] = 'button';
    bCancel.appendText(Strings.get("button.Cancel"));
    bCancel.onClick.listen((h.MouseEvent event) => div1.remove());
    div_buttons.append(bCancel);
    h.ButtonElement bOk = new h.ButtonElement();
    bOk.id = 'file_ok';
    bOk.disabled = true;
    bOk.attributes['type'] = 'submit';
    bOk.appendText(Strings.get("button.OK"));
    bOk.onClick.listen((h.MouseEvent event) => ok(event));
    div_buttons.append(bOk);
    form.append(div_buttons);
    
    div3.append(form);
    
    div2.append(div3);
    div1.append(div2);
    h.document.body.append(div1);
  }
  
  void onChange(h.Event event) {
    h.ButtonElement bOk = h.query('button#file_ok');
    bOk.disabled = false;
  }
  
  void ok(h.MouseEvent event) {
    event.preventDefault();
    h.FileUploadInputElement input = h.query('input#file_input');
    List<h.File> files = input.files;
    if (files.length > 0)
      file = files[0];
    h.query('div#dlg1').remove();
    okfct();
  }
  
  h.File getFile() {
    return(file);
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/find_dialog.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/find_dialog.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

/**
 * Find and Replace dialog.
 */
class FindDialog {
  
  static bool caseSensitive = false;
  static bool backwards = false;
  static String findString = '';
  
  void show() {
    h.DivElement div_find = h.document.getElementById('find_dlg');
    if (div_find != null) {
      h.TextInputElement inputFind = h.document.getElementById('find_dlg_find_field');
      inputFind.focus();
      return;
    }
    h.Element divdoc = h.querySelector("#doc1");
    divdoc.style.bottom = '10.5em';
    div_find = new h.DivElement();
    div_find.id = 'find_dlg';
    div_find.classes.add('find');
    
    h.FormElement form = new h.FormElement();
    h.TableElement table = new h.TableElement();
    h.TableRowElement tr = new h.TableRowElement();
    h.TableCellElement td = new h.TableCellElement();
    td.text = Strings.get('find.find');
    tr.append(td);
    td = new h.TableCellElement();
    h.TextInputElement inputFind = new h.TextInputElement();
    inputFind
      ..id = 'find_dlg_find_field'
      ..name = 'find'
      ..size = 40
      ..value = findString;
    td.append(inputFind);
    tr.append(td);
    table.append(tr);
    tr = new h.TableRowElement();
    td = new h.TableCellElement();
    td.text = Strings.get('find.replace_by');
    tr.append(td);
    td = new h.TableCellElement();
    h.TextInputElement inputReplace = new h.TextInputElement();
    inputReplace
      ..id = 'find_dlg_replace_field'
      ..name = 'replace_by'
      ..size = 40;
    td.append(inputReplace);
    tr.append(td);
    table.append(tr);
    div_find.append(table);
    
    h.DivElement div_options = new h.DivElement();
    div_options.classes.add('options');
    h.CheckboxInputElement cbCaseSensitive = new h.CheckboxInputElement();
    cbCaseSensitive
      ..id = 'find_cb_ignore_case'
      ..checked = caseSensitive
      ..onChange.listen((h.Event event) => caseSensitive = cbCaseSensitive.checked);
    div_options.append(cbCaseSensitive);
    h.LabelElement labelIgnoreCase = new h.LabelElement();
    labelIgnoreCase
      ..htmlFor = 'find_cb_ignore_case'
      ..text = Strings.get("find.case_sensitive");
    div_options.append(labelIgnoreCase);
    h.CheckboxInputElement cbBackwards = new h.CheckboxInputElement();
    cbBackwards
      ..id = 'find_cb_backwards'
      ..checked = backwards
      ..onChange.listen((h.Event event) => backwards = cbBackwards.checked);
    div_options.append(cbBackwards);
    h.LabelElement labelBackwards = new h.LabelElement();
    labelBackwards
      ..htmlFor = 'find_cb_backwards'
      ..text = Strings.get("find.backwards");
    div_options.append(labelBackwards);
    // TODO: option to look at attribute values, XPath search
    form.append(div_options);
    
    h.DivElement div_buttons = new h.DivElement();
    div_buttons.classes.add('buttons');
    h.ButtonElement bClose = new h.ButtonElement();
    bClose
      ..attributes['type'] = 'button'
      ..appendText(Strings.get("button.Close"))
      ..onClick.listen((h.MouseEvent event) => close());
    div_buttons.append(bClose);
    h.ButtonElement bReplace = new h.ButtonElement();
    bReplace
      ..attributes['type'] = 'button'
      ..appendText(Strings.get("find.replace"))
      ..onClick.listen((h.MouseEvent event) => replace());
    div_buttons.append(bReplace);
    h.ButtonElement bReplaceFind = new h.ButtonElement();
    bReplaceFind
      ..attributes['type'] = 'button'
      ..appendText(Strings.get("find.replace_find"))
      ..onClick.listen((h.MouseEvent event) => replaceFind());
    div_buttons.append(bReplaceFind);
    h.ButtonElement bReplaceAll = new h.ButtonElement();
    bReplaceAll
      ..attributes['type'] = 'button'
      ..appendText(Strings.get("find.replace_all"))
      ..onClick.listen((h.MouseEvent event) => replaceAll());
    div_buttons.append(bReplaceAll);
    h.ButtonElement bNext = new h.ButtonElement();
    bNext
      ..attributes['type'] = 'button'
      ..appendText(Strings.get("find.next"))
      ..onClick.listen((h.MouseEvent event) => next());
    div_buttons.append(bNext);
    form.append(div_buttons);
    
    div_find.append(form);
    h.document.body.append(div_find);
    div_find.onKeyDown.listen((h.KeyboardEvent event) {
      if (event.keyCode == h.KeyCode.ESC) {
        close();
      }
    });
    inputFind.focus();
  }
  
  void next() {
    //FIXME: does not work with DNForm and DNSimpleType: selection is not visible
    //  (but then, how could we select a part of a select element anyway ?)
    findString = ((h.document.getElementById('find_dlg_find_field')) as h.TextInputElement).value;
    if (findString == '')
      return;
    Position pos;
    if (!backwards) {
      Position end = page.getSelectionEnd();
      if (end == null)
        end = new Position(doc.dndoc, 0);
      pos = nextAt(end);
    } else {
      Position start = page.getSelectionStart();
      if (start == null)
        start = new Position(doc.dndoc, doc.dndoc.offsetLength);
      pos = previousAt(start);
    }
    if (pos != null) {
      page.moveCursorTo(pos);
      page.cursor.setSelection(pos, new Position(pos.dn, pos.dnOffset + findString.length));
    }
  }
  
  Position nextAt(Position pos) {
    DaxeNode parent = pos.dn;
    int offset = pos.dnOffset;
    if (parent is! DNText) {
      parent = parent.childAtOffset(offset);
      offset = 0;
    }
    while (parent != null) {
      if (parent is DNText) {
        int index;
        if (!caseSensitive)
          index = parent.nodeValue.toLowerCase().indexOf(findString.toLowerCase(), offset);
        else
          index = parent.nodeValue.indexOf(findString, offset);
        if (index != -1)
          return(new Position(parent, index));
      }
      parent = parent.nextNode();
      offset = 0;
    }
    return(null);
  }
  
  Position previousAt(Position pos) {
    DaxeNode parent = pos.dn;
    int offset = pos.dnOffset;
    if (parent is! DNText) {
      if (offset > 0)
        parent = parent.childAtOffset(offset-1);
      else {
        DaxeNode p = parent;
        parent = null;
        while (p != null) {
          if (p.previousSibling != null) {
            parent = p.previousSibling;
            break;
          }
          p = p.parent;
        }
      }
      if (parent != null)
        offset = parent.offsetLength;
    }
    while (parent != null) {
      if (parent is DNText) {
        int index;
        if (!caseSensitive)
          index = parent.nodeValue.toLowerCase().substring(0, offset).lastIndexOf(findString.toLowerCase());
        else
          index = parent.nodeValue.substring(0, offset).lastIndexOf(findString);
        if (index != -1)
          return(new Position(parent, index));
      }
      parent = parent.previousNode();
      if (parent != null)
        offset = parent.offsetLength;
    }
    return(null);
  }
  
  void replace() {
    Position start = new Position.clone(page.getSelectionStart());
    Position end = new Position.clone(page.getSelectionEnd());
    if (start == null || start.dn is! DNText)
      return;
    String replaceString = ((h.document.getElementById('find_dlg_replace_field')) as h.TextInputElement).value;
    UndoableEdit edit = new UndoableEdit.compound(Strings.get('find.replace'));
    // we should not remove the whole string before inserting the new one, because this might
    // cause the text node referenced by pos to disappear, and the insert won't work
    // -> we do the insert first to make sure not to remove a text node
    if (replaceString != '')
      edit.addSubEdit(new UndoableEdit.insertString(end, replaceString));
    if (start != end && start.dn == end.dn)
      edit.addSubEdit(new UndoableEdit.removeString(start, end.dnOffset - start.dnOffset));
    doc.doNewEdit(edit);
    page.cursor.setSelection(start, new Position(start.dn, start.dnOffset + replaceString.length));
  }
  
  void replaceFind() {
    replace();
    next();
  }
  
  void replaceAll() {
    findString = ((h.document.getElementById('find_dlg_find_field')) as h.TextInputElement).value;
    if (findString == '')
      return;
    String replaceString = ((h.document.getElementById('find_dlg_replace_field')) as h.TextInputElement).value;
    Position pos = previousAt(new Position(doc.dndoc, doc.dndoc.offsetLength));
    // we are going backwards in order to be able to combine edits
    UndoableEdit edit = new UndoableEdit.compound(Strings.get('find.replace_all'));
    while (pos != null) {
      if (replaceString != '')
        edit.addSubEdit(new UndoableEdit.insertString(new Position(pos.dn, pos.dnOffset + findString.length),
            replaceString));
      edit.addSubEdit(new UndoableEdit.removeString(pos, findString.length));
      pos = previousAt(pos);
    }
    doc.doNewEdit(edit);
  }
  
  void close() {
    h.DivElement div_find = h.document.getElementById('find_dlg');
    div_find.remove();
    h.Element divdoc = h.querySelector("#doc1");
    divdoc.style.bottom = '1.5em';
    page.focusCursor();
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/help_dialog.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/help_dialog.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

/**
 * Help dialog for an element or an attribute.
 */
class HelpDialog {
  x.Element elementRef;
  x.Element attributeRef;
  StreamSubscription<h.KeyboardEvent> keyboardSubscription;
  
  HelpDialog.Element(this.elementRef) {
  }
  
  HelpDialog.Attribute(this.attributeRef, this.elementRef) {
  }
  
  void show() {
    h.DivElement div1 = new h.DivElement();
    div1.id = 'dlg1';
    div1.classes.add('dlg1');
    h.DivElement div2 = new h.DivElement();
    div2.classes.add('dlg2');
    h.DivElement div3 = new h.DivElement();
    div3.classes.add('dlg3');
    
    // top-right close button
    h.DivElement topDiv = new h.DivElement();
    topDiv.style.position = 'absolute';
    topDiv.style.top = '0px';
    topDiv.style.right = '0px';
    topDiv.style.width = '16px';
    topDiv.style.height = '16px';
    h.ImageElement img = new h.ImageElement();
    img.src = 'packages/daxe/images/close_dialog.png';
    img.width = 16;
    img.height = 16;
    img.style.position = 'fixed';
    img.onClick.listen((h.MouseEvent event) => close());
    topDiv.append(img);
    div3.append(topDiv);
    
    h.DivElement title = new h.DivElement();
    title.classes.add('dlgtitle');
    if (attributeRef == null)
      title.text = doc.cfg.elementTitle(elementRef);
    else
      title.text = doc.cfg.attributeTitle(elementRef, attributeRef);
    div3.append(title);
    
    if (attributeRef == null) {
      h.ParagraphElement p = new h.ParagraphElement();
      p.appendText(Strings.get('help.element_name') + ' ');
      h.SpanElement nameSpan = new h.SpanElement();
      nameSpan.classes.add('help_element_name');
      nameSpan.text = doc.cfg.elementName(elementRef);
      p.append(nameSpan);
      div3.append(p);
    }
    
    String documentation;
    if (attributeRef == null)
      documentation = doc.cfg.documentation(elementRef);
    else
      documentation = doc.cfg.attributeDocumentation(elementRef, attributeRef);
    if (documentation != null) {
      documentation = Config.formatDoc(documentation);
      h.ParagraphElement p = new h.Element.html("<p>$documentation</p>");
      div3.append(p);
    }
    
    if (attributeRef == null) {
      String regexp = doc.cfg.regularExpression(elementRef);
      if (regexp != null) {
        h.DivElement divRegExp = new h.DivElement();
        divRegExp.classes.add('help_regexp');
        divRegExp.text = regexp;
        div3.append(divRegExp);
      }
      h.SpanElement titleParents = new h.SpanElement();
      titleParents.id = 'help_parents';
      titleParents.classes.add('help_list_title');
      titleParents.text = Strings.get('help.parents');
      titleParents.onClick.listen((h.MouseEvent event) => fillParents());
      div3.append(titleParents);
      h.SpanElement titleChildren = new h.SpanElement();
      titleChildren.id = 'help_children';
      titleChildren.classes.add('help_list_title');
      titleChildren.text = Strings.get('help.children');
      titleChildren.onClick.listen((h.MouseEvent event) => fillChildren());
      div3.append(titleChildren);
      h.SpanElement titleAttributes = new h.SpanElement();
      titleAttributes.id = 'help_attributes';
      titleAttributes.classes.add('help_list_title');
      titleAttributes.text = Strings.get('help.attributes');
      titleAttributes.onClick.listen((h.MouseEvent event) => fillAttributes());
      div3.append(titleAttributes);
      h.DivElement divList = new h.DivElement();
      divList.classes.add('help_list_div');
      h.UListElement ul = new h.UListElement();
      ul.id = 'help_list';
      divList.append(ul);
      div3.append(divList);
    }
    
    h.DivElement div_buttons = new h.DivElement();
    div_buttons.classes.add('buttons');
    h.ButtonElement bOk = new h.ButtonElement();
    bOk.attributes['type'] = 'submit';
    bOk.appendText(Strings.get("button.Close"));
    bOk.onClick.listen((h.MouseEvent event) => close());
    div_buttons.append(bOk);
    div3.append(div_buttons);
    
    div2.append(div3);
    div1.append(div2);
    h.document.body.append(div1);
    
    if (div3.clientHeight > div1.clientHeight * 3 / 4) {
      // enlarge dialog width for large content models
      div2.style.left = "33%";
      div3.style.left = "-25%";
    }
    
    if (attributeRef == null)
      fillChildren();
    
    keyboardSubscription = h.document.onKeyDown.listen(null);
    keyboardSubscription.onData((h.KeyboardEvent event) {
      if (event.keyCode == h.KeyCode.ESC) {
        close();
      }
    });
    
    bOk.focus();
  }
  
  void fillParents() {
    h.SpanElement titleParents = h.document.getElementById('help_parents');
    titleParents.classes.add('selected_tab');
    h.SpanElement titleChildren = h.document.getElementById('help_children');
    titleChildren.classes.remove('selected_tab');
    h.SpanElement titleAttributes = h.document.getElementById('help_attributes');
    titleAttributes.classes.remove('selected_tab');
    h.UListElement ul = h.document.getElementById('help_list');
    ul.nodes.clear();
    List<x.Element> parents = doc.cfg.parentElements(elementRef);
    if (parents == null || parents.length == 0)
      return;
    HashMap<x.Element, String> titleMap = bestTitles(parents.toSet());
    parents.sort((ref1, ref2) => titleMap[ref1].toLowerCase().compareTo(
        titleMap[ref2].toLowerCase()));
    for (x.Element parentRef in parents) {
      h.LIElement li = new h.LIElement();
      li.text = titleMap[parentRef];
      String documentation = doc.cfg.documentation(parentRef);
      if (documentation != null)
        li.title = documentation;
      li.onClick.listen((h.MouseEvent event) => switchToElement(parentRef));
      li.classes.add('help_selectable');
      ul.append(li);
    }
  }
  
  /**
   * Look for titles with additional info when several elements have the same name.
   */
  HashMap<x.Element, String> bestTitles(Set<x.Element> refs, [int level=0]) {
    HashMap<String, Set<x.Element>> titleSets = new HashMap<String, Set<x.Element>>();
    // create the map of titles including the element having a common ancestor title at given level
    for (x.Element ref in refs) {
      Set<x.Element> ancestors = ancestorsAtLevel(ref, level);
      // check if ancestors have the same title and if so add title
      String ancestorTitle = doc.cfg.elementTitle(ancestors.first);
      if (ancestorTitle != null) {
        bool sameTitle = true;
        for (x.Element ancestor in ancestors) {
          if (doc.cfg.elementTitle(ancestor) != ancestorTitle) {
            sameTitle = false;
            break;
          }
        }
        if (sameTitle) {
          String title;
          if (level == 0)
            title = doc.cfg.elementTitle(ref);
          else
            title = doc.cfg.elementTitle(ref) + " (" + ancestorTitle + ")";
          Set set = titleSets[title];
          if (set == null) {
            set = new HashSet<x.Element>();
            titleSets[title] = set;
          }
          set.add(ref);
        }
      }
    }
    // go to the next level for elements with the same title
    // and create the result map.
    HashMap<x.Element, String> resMap = new HashMap<x.Element, String>();
    for (String title in titleSets.keys) {
      Set<x.Element> set = titleSets[title];
      if (set.length > 1 && level < 10) {
        HashMap<x.Element, String> map2 = bestTitles(set, level+1);
        resMap.addAll(map2);
        for (x.Element ref in set)
          if (map2[ref] == null) {
            // could not find a unique title for this one, adding the type could be useful...
            if (title.indexOf('(') == -1 && ref.getAttribute('type') != '')
              resMap[ref] = title + " (" + ref.getAttribute('type') + ")";
            else
              resMap[ref] = title;
          }
      } else {
        for (x.Element ref in set)
          resMap[ref] = title;
      }
    }
    return(resMap);
  }
  
  Set<x.Element> ancestorsAtLevel(x.Element ref, int level) {
    Set<x.Element> ancestors = new Set<x.Element>();
    ancestors.add(ref);
    for (int i=0; i<level; i++) {
      Set<x.Element> ancestors2 = new Set<x.Element>();
      for (x.Element ref2 in ancestors) {
        List<x.Element> parentList = doc.cfg.parentElements(ref2);
        if (parentList != null)
          ancestors2.addAll(parentList);
      }
      ancestors = ancestors2;
    }
    return(ancestors);
  }
  
  void fillChildren() {
    h.SpanElement titleParents = h.document.getElementById('help_parents');
    titleParents.classes.remove('selected_tab');
    h.SpanElement titleChildren = h.document.getElementById('help_children');
    titleChildren.classes.add('selected_tab');
    h.SpanElement titleAttributes = h.document.getElementById('help_attributes');
    titleAttributes.classes.remove('selected_tab');
    h.UListElement ul = h.document.getElementById('help_list');
    ul.nodes.clear();
    List<x.Element> children = doc.cfg.subElements(elementRef);
    if (children == null || children.length == 0)
      return;
    HashMap<x.Element, String> titleMap = new HashMap.fromIterable(children,
        value:(x.Element ref) => doc.cfg.elementTitle(ref));
    children.sort((ref1, ref2) => titleMap[ref1].toLowerCase().compareTo(
        titleMap[ref2].toLowerCase()));
    for (x.Element childRef in children) {
      h.LIElement li = new h.LIElement();
      li.text = titleMap[childRef];
      String documentation = doc.cfg.documentation(childRef);
      if (documentation != null)
        li.title = documentation;
      li.onClick.listen((h.MouseEvent event) => switchToElement(childRef));
      li.classes.add('help_selectable');
      ul.append(li);
    }
  }
  
  void fillAttributes() {
    h.SpanElement titleParents = h.document.getElementById('help_parents');
    titleParents.classes.remove('selected_tab');
    h.SpanElement titleChildren = h.document.getElementById('help_children');
    titleChildren.classes.remove('selected_tab');
    h.SpanElement titleAttributes = h.document.getElementById('help_attributes');
    titleAttributes.classes.add('selected_tab');
    h.UListElement ul = h.document.getElementById('help_list');
    ul.nodes.clear();
    List<x.Element> attributes = doc.cfg.elementAttributes(elementRef);
    if (attributes == null || attributes.length == 0)
      return;
    HashMap<x.Element, String> titleMap = new HashMap.fromIterable(attributes,
        value:(x.Element attRef) => doc.cfg.attributeTitle(elementRef, attRef));
    attributes.sort((ref1, ref2) => titleMap[ref1].toLowerCase().compareTo(
        titleMap[ref2].toLowerCase()));
    for (x.Element attRef in attributes) {
      h.LIElement li = new h.LIElement();
      li.text = titleMap[attRef];
      String documentation = doc.cfg.attributeDocumentation(elementRef, attRef);
      if (documentation != null)
        li.title = documentation;
      ul.append(li);
    }
  }
  
  void switchToElement(x.Element elementRef) {
    this.elementRef = elementRef;
    attributeRef = null;
    close();
    show();
  }
  
  void close() {
    keyboardSubscription.cancel();
    h.DivElement div1 = h.document.getElementById('dlg1');
    div1.remove();
    page.focusCursor();
  }
}



Index: modules/damieng/graphical_editor/daxe/lib/src/insert_panel.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/insert_panel.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

/**
 * Left panel to insert elements. It only displays the elements that can be inserted at
 * the cursor position, according to the schema.
 */
class InsertPanel {
  
  void update(DaxeNode parent, List<x.Element> refs, List<x.Element> validRefs) {
    h.Element divInsert = h.document.getElementById('insert');
    for (h.Element child in divInsert.children)
      child.remove();
    Config cfg = doc.cfg;
    if (cfg == null)
      return;
    if (parent.nodeType == DaxeNode.ELEMENT_NODE && parent.ref != null) {
      divInsert.append(_makeHelpButton(parent.ref));
      String name = cfg.elementName(parent.ref);
      h.SpanElement span = new h.SpanElement();
      span.appendText(cfg.menuTitle(name));
      divInsert.append(span);
      divInsert.append(new h.HRElement());
    }
    List<x.Element> toolbarRefs;
    if (page.toolbar != null)
      toolbarRefs = page.toolbar.elementRefs();
    else
      toolbarRefs = null;
    for (x.Element ref in refs) {
      if (toolbarRefs != null && toolbarRefs.contains(ref))
        continue;
      if (doc.hiddenParaRefs != null && doc.hiddenParaRefs.contains(ref))
        continue;
      divInsert.append(_makeHelpButton(ref));
      h.ButtonElement button = new h.ButtonElement();
      button.attributes['type'] = 'button';
      button.classes.add('insertb');
      String name = cfg.elementName(ref);
      button.value = name;
      button.text = cfg.menuTitle(name);
      if (!validRefs.contains(ref))
        button.disabled = true;
      button.onClick.listen((h.Event event) => insert(ref));
      button.onKeyDown.listen((h.KeyboardEvent event) {
        int keyCode = event.keyCode;
        if (keyCode == h.KeyCode.ENTER) {
          event.preventDefault();
          insert(ref);
        }
      });
      divInsert.append(button);
      divInsert.append(new h.BRElement());
    }
  }
  
  h.ButtonElement _makeHelpButton(x.Element ref) {
    h.ButtonElement bHelp = new h.ButtonElement();
    bHelp.attributes['type'] = 'button';
    bHelp.classes.add('help');
    bHelp.value = '?';
    bHelp.text = '?';
    String documentation = doc.cfg.documentation(ref);
    if (documentation != null)
      bHelp.title = documentation;
    bHelp.onClick.listen((h.Event event) => help(ref));
    return(bHelp);
  }
  
  void insert(x.Element ref) {
    doc.insertNewNode(ref, 'element');
  }
  
  void help(x.Element ref) {
    HelpDialog dlg = new HelpDialog.Element(ref);
    dlg.show();
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/interface_schema.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/interface_schema.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

library InterfaceSchema;

import 'xmldom/xmldom.dart';


/**
 * Interface for a schema language, such as the W3C one, Relax NG, or Jaxe's simple schemas.
 * This interface is using the notion of "element reference" which corresponds to the schema element
 * defining the XML element (this assumes that schemas are XML trees).
 */
abstract class InterfaceSchema {
  
  /**
   * Returns true if the reference comes from this schema
   */
  bool elementInSchema(final Element elementRef);
  
  /**
   * Returns the reference for the first schema element with the given name.
   */
  Element elementReferenceByName(final String name);
  
  /**
   * Returns all the references of the schema elements with the given name.
   */
  List<Element> elementReferencesByName(final String name);
  
  /**
   * Returns the reference for the first schema element with the given name and namespace,
   * and with the parent whose reference is given.
   */
  Element elementReference(final Element el, final Element parentRef);
  
  /**
   * Returns the name of the element.
   */
  String elementName(final Element elementRef);
  
  /**
   * Returns the namespace of the element, or null if it is not defined.
   */
  String elementNamespace(final Element elementRef);
  
  /**
   * Returns the prefix to use to create a new element with the given reference,
   * or null if no prefix should be used.
   */
  String elementPrefix(final Element elementRef);
  
  /**
   * Returns the documentation for an element
   * (in a simple text format, with \n for line breaks).
   */
  String elementDocumentation(final Element elementRef);
  
  /**
   * Returns the list of possible values for an element.
   * Returns null if there are an infinite number of possible values,
   * or if the element does not have a simple type.
   */
  List<String> elementValues(final Element elementRef);
  
  /**
   * Returns a list of values to suggest to the user for an element.
   * This is only useful when there is an infinite number of possible values.
   * Returns null if there is no interesting value to suggest,
   * or if the element does not have a simple type.
   */
  List<String> suggestedElementValues(final Element elementRef);
  
  /**
   * Returns true if the given value is a valid value for the element.
   */
  bool elementValueIsValid(final Element elementRef, final String value);
  
  /**
   * Returns the list of namespaces used by this schema.
   */
  List<String> namespaceList();
  
  /**
   * Returns true if the namespace is defined in this schema.
   */
  bool hasNamespace(final String namespace);
  
  /**
   * Returns a prefix to use for the given namespace, or null if none is found.
   */
  String namespacePrefix(final String ns);
  
  /**
   * Returns the target namespace for the schema.
   * Warning: the concept of a unique target namespace does not exist with Relax NG.
   */
  String getTargetNamespace();
  
  /**
   * Returns the references of the elements which are not in the given namespace.
   */
  List<Element> elementsOutsideNamespace(final String namespace);
  
  /**
   * Returns the references of the elements which are in the given namespaces.
   */
  List<Element> elementsWithinNamespaces(final Set<String> namespaces);
  
  /**
   * Returns the references for all the schema elements.
   */
  List<Element> allElements();
  
  /**
   * Returns the references for all the possible root elements.
   */
  List<Element> rootElements();
  
  /**
   * Returns true if the child is required under the parent.
   */
  bool requiredElement(final Element parentRef, final Element childRef);
  
  /**
   * Returns true if the parent can have multiple children with the given child.
   */
  bool multipleChildren(final Element parentRef, final Element childRef);
  
  /**
   * Returns the references for the children of the given parent.
   */
  List<Element> subElements(final Element parentRef);
  
  /**
   * Regular expression for a given parent element
   * [modevisu]  True to get a regular expression to display to the user
   * [modevalid]  For strict validation instead of checking if an insert is possible
   */
  String regularExpression(final Element parentRef, final bool modevisu, final bool modevalid);
  
  /**
   * Returns the references for an element's possible parents.
   */
  List<Element> parentElements(final Element elementRef);
  
  /**
   * Returns the references for an element's possible attributes.
   */
  List<Element> elementAttributes(final Element elementRef);
  
  /**
   * Returns an attribute name.
   */
  String attributeName(final Element attributeRef);
  
  /**
   * Returns an attribute namespace, or null if none is defined.
   */
  String attributeNamespace(final Element attributeRef);
  
  /**
   * Returns an attribute documentation.
   */
  String attributeDocumentation(final Element attributeRef);
  
  /**
   * Returns an attribute namespace based on its full name.
   */
  String attributeNamespaceByName(final String attributeName);
  
  /**
   * Returns true if the attribute is required under the parent.
   */
  bool attributeIsRequired(final Element refParent, final Element attributeRef);
  
  /**
   * Returns the possible values for an attribute.
   * Returns null if there are an infinity of possible values.
   */
  List<String> attributeValues(final Element attributeRef);
  
  /**
   * Returns a list of values to suggest to the user for an attribute.
   * This is only useful when there is an infinite number of possible values.
   * Returns null if there is no interesting value to suggest.
   */
  List<String> suggestedAttributeValues(final Element attributeRef);
  
  /**
   * Returns an attribute default value.
   */
  String defaultAttributeValue(final Element attributeRef);
  
  /**
   * Returns true if the given value is valid for the attribute.
   */
  attributeIsValid(final Element attributeRef, final String value);
  
  /**
   * Returns the reference to the first possible parent element for a given attribute.
   */
  Element attributeParent(final Element attributeRef);
  
  /**
   * Returns true if the element can contain text.
   */
  bool canContainText(final Element elementRef);
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/left_offset_position.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/left_offset_position.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

/**
 * A position in the XML document based on the document offset from the start.
 */
class LeftOffsetPosition implements Position {
  int _leftOffset;
  
  LeftOffsetPosition(int leftOffset) {
    assert(leftOffset >= 0);
    _leftOffset = leftOffset;
  }
  
  LeftOffsetPosition.fromNodeOffsetPosition(NodeOffsetPosition nopos) {
    DaxeNode targetDn = nopos.dn;
    int targetDnOffset = nopos.dnOffset;
    _leftOffset = 0;
    DaxeNode dn = doc.dndoc;
    int dnOffset = 0;
    while (dn != targetDn || dnOffset != targetDnOffset) {
      if (dnOffset == dn.offsetLength) {
        dnOffset = dn.parent.offsetOf(dn) + 1;
        dn = dn.parent;
      } else if (dn is DNText) {
        dnOffset++;
      } else {
        dn = dn.childAtOffset(dnOffset);
        dnOffset = 0;
      }
      _leftOffset++;
    }
  }
  
  LeftOffsetPosition.fromRightOffsetPosition(RightOffsetPosition ropos) {
    DaxeNode targetDn = doc.dndoc;
    int targetDnOffset = targetDn.offsetLength;
    int offset = 0;
    DaxeNode dn = doc.dndoc;
    int dnOffset = 0;
    while (dn != targetDn || dnOffset != targetDnOffset) {
      if (dnOffset == dn.offsetLength) {
        dnOffset = dn.parent.offsetOf(dn) + 1;
        dn = dn.parent;
      } else if (dn is DNText) {
        dnOffset++;
      } else {
        dn = dn.childAtOffset(dnOffset);
        dnOffset = 0;
      }
      offset++;
    }
    _leftOffset = offset - ropos.rightOffset;
  }
  
  LeftOffsetPosition.clone(LeftOffsetPosition pos) {
    _leftOffset = pos.leftOffset;
  }
  
  DaxeNode get dn {
    NodeOffsetPosition nopos = new NodeOffsetPosition.fromLeftOffsetPosition(this);
    return(nopos.dn);
  }
  
  int get dnOffset {
    NodeOffsetPosition nopos = new NodeOffsetPosition.fromLeftOffsetPosition(this);
    return(nopos.dnOffset);
  }
  
  int get leftOffset {
    return(_leftOffset);
  }
  
  int get rightOffset {
    RightOffsetPosition ropos = new RightOffsetPosition.fromLeftOffsetPosition(this);
    return(ropos.rightOffset);
  }
  
  bool operator ==(Position other) {
    return(_leftOffset == other.leftOffset);
  }
  
  bool operator <(Position other) {
    return(_leftOffset < other.leftOffset);
  }
  
  bool operator <=(Position other) {
    return(_leftOffset <= other.leftOffset);
  }
  
  bool operator >(Position other) {
    return(_leftOffset > other.leftOffset);
  }
  
  bool operator >=(Position other) {
    return(_leftOffset >= other.leftOffset);
  }
  
  void move(int offset) {
    _leftOffset += offset;
  }
  
  void moveInsideTextNodeIfPossible() {
    NodeOffsetPosition nopos = new NodeOffsetPosition.fromLeftOffsetPosition(this);
    nopos.moveInsideTextNodeIfPossible();
    _leftOffset = nopos.leftOffset;
  }
  
  Point positionOnScreen() {
    NodeOffsetPosition nopos = new NodeOffsetPosition.fromLeftOffsetPosition(this);
    return(nopos.positionOnScreen());
  }
  
  String xPath({bool titles:false}) {
    NodeOffsetPosition nopos = new NodeOffsetPosition.fromLeftOffsetPosition(this);
    return(nopos.xPath(titles:titles));
  }
  
  String toString() {
    return("[LeftOffsetPosition $_leftOffset]");
  }
  
}
Index: modules/damieng/graphical_editor/daxe/lib/src/left_panel.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/left_panel.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

/**
 * Left panel, with the insert and tree tabs.
 */
class LeftPanel {
  int _selected;
  InsertPanel _insertP;
  TreePanel _treeP;
  
  LeftPanel() {
    _selected = 0;
    _insertP = new InsertPanel();
    _treeP = new TreePanel();
  }
  
  h.DivElement html() {
    h.DivElement panelDiv = new h.DivElement();
    panelDiv.id = 'left_panel';
    h.DivElement buttonsDiv = new h.DivElement();
    buttonsDiv.id = 'tab_buttons';
    h.SpanElement insertButton = new h.SpanElement();
    insertButton.id = 'insert_tab_button';
    insertButton.classes.add('tab_button');
    insertButton.classes.add('selected');
    insertButton.setAttribute('tabindex', '-1');
    insertButton.appendText(Strings.get('left.insert'));
    insertButton.onClick.listen((h.MouseEvent event) => selectInsertPanel());
    insertButton.onKeyDown.listen((h.KeyboardEvent event) {
      int keyCode = event.keyCode;
      if (keyCode == h.KeyCode.ENTER || keyCode == h.KeyCode.DOWN) {
        h.Element divInsert = h.document.getElementById('insert');
        if (divInsert.firstChild is h.ButtonElement) {
          event.preventDefault();
          (divInsert.firstChild as h.ButtonElement).focus();
        }
      } else if (keyCode == h.KeyCode.RIGHT) {
        selectTreePanel();
      } else if (keyCode == h.KeyCode.TAB) {
        Timer.run( () {
          page.cursor.show();
          page.cursor.focus();
        });
      }
    });
    buttonsDiv.append(insertButton);
    h.SpanElement treeButton = new h.SpanElement();
    treeButton.id = 'tree_tab_button';
    treeButton.classes.add('tab_button');
    treeButton.setAttribute('tabindex', '-1');
    treeButton.appendText(Strings.get('left.tree'));
    treeButton.onClick.listen((h.MouseEvent event) => selectTreePanel());
    treeButton.onKeyDown.listen((h.KeyboardEvent event) {
      int keyCode = event.keyCode;
      if (keyCode == h.KeyCode.ENTER || keyCode == h.KeyCode.DOWN) {
        if (_treeP.rootItem != null)
          _treeP.rootItem.focus();
      } else if (keyCode == h.KeyCode.LEFT) {
        selectInsertPanel();
      } else if (keyCode == h.KeyCode.TAB) {
        Timer.run( () {
          page.cursor.show();
          page.cursor.focus();
        });
      }
    });
    buttonsDiv.append(treeButton);
    panelDiv.append(buttonsDiv);
    h.DivElement insertDiv = new h.DivElement();
    insertDiv.id = 'insert';
    panelDiv.append(insertDiv);
    h.DivElement treeDiv = new h.DivElement();
    treeDiv.id = 'tree';
    treeDiv.style.display = 'none';
    panelDiv.append(treeDiv);
    return(panelDiv);
  }
  
  void selectInsertPanel() {
    h.document.getElementById('insert').style.display = 'block';
    h.document.getElementById('tree').style.display = 'none';
    h.document.getElementById('insert_tab_button').classes.add('selected');
    h.document.getElementById('tree_tab_button').classes.remove('selected');
    h.document.getElementById('insert_tab_button').setAttribute('tabindex', '0');
    h.document.getElementById('tree_tab_button').setAttribute('tabindex', '-1');
    h.document.getElementById('insert_tab_button').focus();
    _selected = 0;
    if (page.getSelectionStart() == null)
      return;
    DaxeNode parent = page.getSelectionStart().dn;
    if (parent is DNText)
      parent = parent.parent;
    List<x.Element> refs = doc.elementsAllowedUnder(parent);
    List<x.Element> validRefs = doc.validElementsInSelection(refs);
    _insertP.update(parent, refs, validRefs);
  }
  
  void selectTreePanel() {
    h.document.getElementById('tree').style.display = 'block';
    h.document.getElementById('insert').style.display = 'none';
    h.document.getElementById('tree_tab_button').classes.add('selected');
    h.document.getElementById('insert_tab_button').classes.remove('selected');
    h.document.getElementById('tree_tab_button').setAttribute('tabindex', '0');
    h.document.getElementById('insert_tab_button').setAttribute('tabindex', '-1');
    h.document.getElementById('tree_tab_button').focus();
    _selected = 1;
    _treeP.update();
  }
  
  void update(DaxeNode parent, List<x.Element> refs, List<x.Element> validRefs) {
    if (_selected == 0)
      _insertP.update(parent, refs, validRefs);
    else
      _treeP.update();
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/locale.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/locale.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

class Locale {
  
  String language;
  String country;
  
  Locale() {
    List<String> l = Strings.systemLocale.split('_');
    language = l[0];
    if (l.length > 1)
      country = l[1];
    else
      country = null;
  }
  
  Locale.l(String language) {
    this.language = language;
    this.country = null;
  }
  
  Locale.lc(String language, String country) {
    this.language = language;
    this.country = country;
  }
  
  int get hashCode {
    int result = 17;
    result = 37 * result + language.hashCode;
    result = 37 * result + country.hashCode;
    return result;
  }
  
  bool operator ==(Locale other) {
    return(language == other.language && country == other.country);
  }
  
}


Index: modules/damieng/graphical_editor/daxe/lib/src/menu.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/menu.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

class Menu extends MenuItem {
  List<MenuItem> items;
  String menuid;
  
  Menu(String title) : super(title, null) {
    items = new List<MenuItem>();
    this.menuid = "menu_${MenuItem.itemidcount-1}";
  }
  
  add(MenuItem item) {
    item.parent = this;
    items.add(item);
  }
  
  @override
  h.Element htmlItem() {
    h.TableRowElement tr = new h.TableRowElement();
    tr.id = itemid;
    tr.setAttribute('tabindex', '-1');
    tr.onKeyDown.listen((h.KeyboardEvent event) {
      if (h.document.activeElement != tr)
        return; // an item could have the focus, and the onKeyDown will trigger
      int keyCode = event.keyCode;
      if (keyCode == h.KeyCode.ENTER) {
        select();
      } else if (keyCode == h.KeyCode.UP) {
        (parent as Menu).selectPrevious(this);
      } else if (keyCode == h.KeyCode.DOWN) {
        (parent as Menu).selectNext(this);
      } else if (keyCode == h.KeyCode.LEFT) {
        if (parent is Menu) {
          if ((parent as Menu).parent is Menu)
            (parent as Menu).select();
          else if ((parent as Menu).parent is MenuBar)
            ((parent as Menu).parent as MenuBar).selectPrevious(parent as Menu);
        }
      } else if (keyCode == h.KeyCode.RIGHT) {
        for (MenuItem item in items) {
          if (item.enabled) {
            item.select();
            break;
          }
        }
      } else if (keyCode == h.KeyCode.TAB) {
        Timer.run(closeMenu);
      }
    });
    h.TableCellElement td = new h.TableCellElement();
    td.text = _title;
    td.onMouseOver.listen((h.MouseEvent event) {
      if (enabled) {
        select();
        show();
      }
    });
    tr.append(td);
    td = new h.TableCellElement();
    td.text = ">";
    h.DivElement divSubMenu = htmlMenu();
    divSubMenu.classes.remove('dropdown_menu');
    divSubMenu.classes.add('submenu');
    divSubMenu.style.display = 'none';
    td.append(divSubMenu);
    td.onMouseOver.listen((h.MouseEvent event) {
      if (enabled) {
        select();
        show();
      }
    });
    tr.append(td);
    if (!enabled)
      tr.classes.add('disabled');
    if (toolTipText != null)
      tr.title = toolTipText;
    return(tr);
  }
  
  h.Element htmlMenu() {
    h.DivElement div = new h.DivElement();
    div.classes.add('dropdown_menu');
    div.id = menuid;
    h.TableElement table = new h.TableElement();
    table.classes.add('menu');
    for (MenuItem item in items) {
      table.append(item.htmlItem());
    }
    div.append(table);
    return(div);
  }
  
  h.Element getMenuHTMLNode() {
    return(h.querySelector("#$menuid"));
  }
  
  void show() {
    h.DivElement div = getMenuHTMLNode();
    div.style.display = 'block';
    //h.SpanElement spanTitle = h.query("#menutitle_$id");
    // to avoid setting coordinates, we use a div with position absolute
    // and top 100% inside a div with position relative...
    //int top = spanTitle.offsetTop + spanTitle.offsetHeight;
    //int left = spanTitle.offsetLeft;
    //hMenu.style.top = "${top}px";
    //hMenu.style.left = "${left}px";
  }
  
  void hide() {
    for (MenuItem item in items) {
      if (item is Menu)
        item.hide();
      item.deselect();
    }
    h.DivElement div = getMenuHTMLNode();
    div.style.display = 'none';
  }
  
  bool isVisible() {
    h.DivElement div = getMenuHTMLNode();
    return(div.style.display != 'none');
  }
  
  void addSeparator() {
    items.add(new MenuItem.separator());
  }
  
  void deselectOtherItems(MenuItem menuitem) {
    for (MenuItem item in items) {
      if (item != menuitem)
        item.deselect();
    }
  }
  
  void set title(String title) {
    _title = title;
    h.Element hel = h.querySelector("#$itemid");
    if (hel is h.TableRowElement) {
      h.TableRowElement tr = hel;
      h.TableCellElement td = tr.nodes.first;
      td.text = title;
    } else if (hel is h.DivElement) {
      h.Node firstNode = hel.firstChild;
      if (firstNode is h.Text)
        firstNode.text = title;
    }
  }
  
  void checkEnabled() {
    bool en = false;
    for (MenuItem item in items) {
      if (item.enabled) {
        en = true;
        break;
      }
    }
    if (en == enabled)
      return;
    h.Element el = h.querySelector("#$itemid");
    if (en)
      el.classes.remove('disabled');
    else
      el.classes.add('disabled');
    enabled = en;
    if (parent is Menu)
      (parent as Menu).checkEnabled();
  }
  
  /*
  @override
  void close() {
    if (parent != null)
      parent.close();
    else
      hide();
  }
  */
  
  void selectFirst() {
    for (MenuItem item in items) {
      if (item.enabled) {
        item.select();
        return;
      }
    }
  }
  
  void selectPrevious(MenuItem current) {
    bool found = false;
    for (MenuItem item in items.reversed) {
      if (item == current) {
        found = true;
      } else if (found && item.enabled) {
        current.deselect();
        item.select();
        break;
      }
    }
  }
  
  void selectNext(MenuItem current) {
    bool found = false;
    for (MenuItem item in items) {
      if (item == current) {
        found = true;
      } else if (found && item.enabled) {
        current.deselect();
        item.select();
        break;
      }
    }
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/menu_item.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/menu_item.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

class MenuItem {
  static int itemidcount = 0;
  String itemid;
  String _title;
  Object parent; // Menu, Menubar or Toolbar
  ActionFunction action;
  String shortcut;
  Object data;
  bool enabled;
  bool selected;
  bool is_separator;
  String toolTipText;
  bool checked;
  
  MenuItem(this._title, this.action, {this.shortcut, this.data}) {
    this.itemid = "item_$itemidcount";
    itemidcount++;
    parent = null;
    enabled = true;
    selected = false;
    is_separator = false;
    checked = false;
  }
  
  MenuItem.separator() {
    is_separator = true;
    enabled = false;
    checked = false;
    selected = false;
  }
  
  h.Element htmlItem() {
    h.TableRowElement tr = new h.TableRowElement();
    if (is_separator) {
      h.TableCellElement td = new h.TableCellElement();
      td.append(new h.HRElement());
      tr.append(td);
    } else {
      tr.id = itemid;
      tr.setAttribute('tabindex', '-1');
      tr.onKeyDown.listen((h.KeyboardEvent event) {
        int keyCode = event.keyCode;
        if (keyCode == h.KeyCode.ENTER) {
          event.preventDefault();
          closeMenu();
          activate();
        } else if (keyCode == h.KeyCode.UP) {
          (parent as Menu).selectPrevious(this);
        } else if (keyCode == h.KeyCode.DOWN) {
          (parent as Menu).selectNext(this);
        } else if (keyCode == h.KeyCode.LEFT) {
          if ((parent as Menu).parent is Menu) {
            (parent as Menu).hide();
            (parent as Menu).getItemHTMLNode().focus();
            event.preventDefault();
            event.stopPropagation();
          }
          else if ((parent as Menu).parent is MenuBar)
            ((parent as Menu).parent as MenuBar).selectPrevious(parent as Menu);
        } else if (keyCode == h.KeyCode.RIGHT) {
          Object ancestor = parent;
          while (ancestor is Menu)
            ancestor = (ancestor as Menu).parent;
          if (ancestor is MenuBar)
            ancestor.selectNext(null);
        } else if (keyCode == h.KeyCode.TAB) {
          Timer.run(closeMenu);
        }
      });
      h.TableCellElement td = new h.TableCellElement();
      td.text = _title;
      td.onMouseUp.listen((h.MouseEvent event) => activate());
      td.onMouseOver.listen((h.MouseEvent event) {
        if (enabled)
          select();
      });
      td.onMouseOut.listen((h.MouseEvent event) => deselect());
      tr.append(td);
      td = new h.TableCellElement();
      if (this.shortcut != null)
        td.text = "Ctrl+$shortcut";
      td.onMouseUp.listen((h.MouseEvent event) => activate());
      td.onMouseOver.listen((h.MouseEvent event) {
        if (enabled)
          select();
      });
      td.onMouseOut.listen((h.MouseEvent event) => deselect());
      if (checked)
        tr.classes.add('checked');
      tr.append(td);
      if (!enabled)
        tr.classes.add('disabled');
    }
    if (toolTipText != null)
      tr.title = toolTipText;
    return(tr);
  }
  
  h.Element getItemHTMLNode() {
    return(h.querySelector("#$itemid"));
  }
  
  void activate() {
    if (!enabled)
      return;
    page.focusCursor();
    action();
  }
  
  void select() {
    if (selected)
      return;
    selected = true;
    h.Element tr = getItemHTMLNode();
    tr.classes.add('selected');
    if (this is Menu) {
      (this as Menu).show();
    }
    if (parent is Menu)
      (parent as Menu).deselectOtherItems(this);
    tr.focus();
  }
  
  void deselect() {
    if (!selected)
      return;
    selected = false;
    h.Element tr = getItemHTMLNode();
    if (tr != null) {
      tr.classes.remove('selected');
      tr.blur();
    }
    if (this is Menu) {
      (this as Menu).hide();
    }
  }
  
  void disable() {
    if (!enabled)
      return;
    enabled = false;
    h.Element tr = getItemHTMLNode();
    tr.classes.remove('selected');
    tr.classes.add('disabled');
    if (parent is Menu)
      (parent as Menu).checkEnabled();
  }
  
  void enable() {
    if (enabled)
      return;
    enabled = true;
    h.Element tr = getItemHTMLNode();
    tr.classes.remove('disabled');
    if (parent is Menu)
      (parent as Menu).checkEnabled();
  }
  
  void check() {
    if (checked)
      return;
    checked = true;
    h.Element tr = getItemHTMLNode();
    tr.classes.add('checked');
  }
  
  void uncheck() {
    if (!checked)
      return;
    checked = false;
    h.Element tr = getItemHTMLNode();
    tr.classes.remove('checked');
  }
  
  /*
  void close() {
    if (parent != null)
      parent.close();
  }
  */
  
  String get title {
    return(_title);
  }
  
  void set title(String title) {
    _title = title;
    h.TableRowElement tr = h.querySelector("#$itemid");
    h.TableCellElement td = tr.nodes.first;
    td.text = title;
  }
  
  void closeMenu() {
    if (parent is! Menu)
      return;
    Menu ancestorMenu = parent;
    while (ancestorMenu.parent is Menu)
      ancestorMenu = ancestorMenu.parent;
    if (ancestorMenu.parent is MenuBar)
      (ancestorMenu.parent as MenuBar).hideVisibleMenu();
    else
      ancestorMenu.hide();
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/menubar.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/menubar.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

class MenuBar {
  List<Menu> menus;
  bool ignoreClick;
  Menu visibleMenu;
  
  MenuBar() {
    menus = new List<Menu>();
    ignoreClick = false;
    visibleMenu = null;
  }
  
  add(Menu m) {
    menus.add(m);
  }
  
  insert(Menu m, int position) {
    menus.insert(position, m);
  }
  
  h.Element html() {
    h.DivElement div = new h.DivElement();
    div.classes.add('menubar');
    bool addedTabindex = false;
    for (Menu m in menus) {
      h.DivElement divMenu = createMenuDiv(m);
      div.append(divMenu);
      if (!addedTabindex && m.enabled) {
        divMenu.setAttribute('tabindex', '0');
        addedTabindex = true;
      }
    }
    h.document.onMouseUp.listen((h.MouseEvent event) => docMouseUp(event));
    return(div);
  }
  
  h.DivElement createMenuDiv(Menu m) {
    h.DivElement divMenu = new h.DivElement();
    divMenu.text = m.title;
    divMenu.id = m.itemid;
    divMenu.classes.add('menu_title');
    if (m.parent is Toolbar)
      divMenu.setAttribute('tabindex', '0');
    else
      divMenu.setAttribute('tabindex', '-1');
    divMenu.onMouseDown.listen((h.MouseEvent event) => mouseDown(event, m));
    divMenu.onMouseOver.listen((h.MouseEvent event) => mouseOver(event, m));
    divMenu.onClick.listen((h.MouseEvent event) => click(m));
    divMenu.onKeyDown.listen((h.KeyboardEvent event) {
      if (h.document.activeElement != divMenu)
        return; // an item could have the focus, and the onKeyDown will trigger
      int keyCode = event.keyCode;
      if (keyCode == h.KeyCode.ENTER || keyCode == h.KeyCode.DOWN) {
        event.preventDefault();
        if (m == visibleMenu)
          m.selectFirst();
        else
          showMenu(m);
      } else if (keyCode == h.KeyCode.LEFT) {
        selectPrevious(m);
      } else if (keyCode == h.KeyCode.RIGHT) {
        selectNext(m);
      } else if (keyCode == h.KeyCode.TAB) {
        Timer.run(hideVisibleMenu);
      }
    });
    h.Element dropdown = m.htmlMenu();
    dropdown.style.display = 'none';
    divMenu.append(dropdown);
    return(divMenu);
  }
  
  void mouseDown(h.MouseEvent event, Menu m) {
    event.preventDefault();
    if (!m.isVisible()) {
      showMenu(m);
      ignoreClick = true;
    } else {
      ignoreClick = false;
    }
  }
  
  void mouseOver(h.MouseEvent event, Menu m) {
    if (visibleMenu == null || visibleMenu == m)
      return;
    hideMenu(visibleMenu);
    showMenu(m);
  }
  
  void click(Menu m) {
    if (ignoreClick) {
      return;
    }
    if (!m.isVisible()) {
      showMenu(m);
    } else {
      hideMenu(m);
    }
  }
  
  void docMouseUp(h.MouseEvent event) {
    if (visibleMenu == null)
      return;
    h.DivElement divMenu = h.querySelector("#${visibleMenu.itemid}");
    h.Rectangle r = divMenu.getBoundingClientRect();
    if (event.client.x < r.left || event.client.x > r.right ||
        event.client.y < r.top || event.client.y > r.bottom) {
      hideMenu(visibleMenu);
      ignoreClick = true;
    }
  }
  
  void showMenu(Menu m) {
    visibleMenu = m;
    h.DivElement divMenu = h.querySelector("#${m.itemid}");
    divMenu.classes.add('selected');
    m.show();
    m.getItemHTMLNode().focus();
  }
  
  void hideMenu(Menu m) {
    visibleMenu = null;
    h.DivElement divMenu = h.querySelector("#${m.itemid}");
    divMenu.classes.remove('selected');
    m.hide();
  }
  
  void hideVisibleMenu() {
    if (visibleMenu != null)
      hideMenu(visibleMenu);
  }
  
  void selectPrevious(Menu current) {
    if (current == null)
      current = visibleMenu;
    if (current == null)
      return;
    bool found = false;
    for (Menu m in menus.reversed) {
      if (m == current) {
        found = true;
      } else if (found && m.enabled) {
        hideMenu(current);
        showMenu(m);
        return;
      }
    }
  }
  
  void selectNext(Menu current) {
    if (current == null)
      current = visibleMenu;
    if (current == null)
      return;
    bool found = false;
    for (Menu m in menus) {
      if (m == current) {
        found = true;
      } else if (found && m.enabled) {
        hideMenu(current);
        showMenu(m);
        return;
      }
    }
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/node_factory.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/node_factory.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

typedef DaxeNode ConstructorFromNode(x.Node node, DaxeNode parent);
typedef DaxeNode ConstructorFromRef(x.Element elementRef);

NodeFactory nodeFactory = new NodeFactory();

/**
 * Factory class to create DaxeNode objects based on an XML node or on a schema reference.
 */
class NodeFactory {
  
  // Note: it would have been nice to use mirrors to avoid defining constructors for each type,
  // but reflection can cause problems because of tree shaking in dart2js:
  // see caveat 4 in http://www.dartlang.org/articles/reflection-with-mirrors/
  // As a result, we are defining constructor functions for each type.
  // Additionnal types can be defined with NodeFactory.addDisplayType
  
  // TODO: add factory constructors in DaxeNode ?
  
  HashMap<String,ConstructorFromRef> constructorsFromRef = new HashMap<String,ConstructorFromRef>();
  
  HashMap<String,ConstructorFromNode> constructorsFromNode = new HashMap<String,ConstructorFromNode>();
  
  static void addCoreDisplayTypes() {
    // add core types in the nodes library
    addDisplayType('anchor',
        (x.Element ref) => new DNAnchor.fromRef(ref),
        (x.Node node, DaxeNode parent) => new DNAnchor.fromNode(node, parent)
    );
    addDisplayType('area',
        (x.Element ref) => new DNArea.fromRef(ref),
        (x.Node node, DaxeNode parent) => new DNArea.fromNode(node, parent)
    );
    addDisplayType('br',
        (x.Element ref) => new DNLineBreak.fromRef(ref),
        (x.Node node, DaxeNode parent) => new DNLineBreak.fromNode(node, parent)
    );
    addDisplayType('champ',
        (x.Element ref) => new DNFormField.fromRef(ref),
        (x.Node node, DaxeNode parent) => new DNFormField.fromNode(node, parent)
    );
    addDisplayType('division',
        (x.Element ref) => new DNDivision.fromRef(ref),
        (x.Node node, DaxeNode parent) => new DNDivision.fromNode(node, parent)
    );
    addDisplayType('empty',
        (x.Element ref) => new DNEmpty.fromRef(ref),
        (x.Node node, DaxeNode parent) => new DNEmpty.fromNode(node, parent)
    );
    addDisplayType('equationmem',
        (x.Element ref) => new DNEquationMem.fromRef(ref),
        (x.Node node, DaxeNode parent) => new DNEquationMem.fromNode(node, parent)
    );
    addDisplayType('equatexmem',
        (x.Element ref) => new DNEquaTexMem.fromRef(ref),
        (x.Node node, DaxeNode parent) => new DNEquaTexMem.fromNode(node, parent)
    );
    addDisplayType('field',
        (x.Element ref) => new DNFormField.fromRef(ref),
        (x.Node node, DaxeNode parent) => new DNFormField.fromNode(node, parent)
    );
    addDisplayType('hr',
          (x.Element ref) => new DNHr.fromRef(ref),
          (x.Node node, DaxeNode parent) => new DNHr.fromNode(node, parent)
      );
    addDisplayType('item',
        (x.Element ref) => new DNItem.fromRef(ref),
        (x.Node node, DaxeNode parent) => new DNItem.fromNode(node, parent)
    );
    addDisplayType('list',
        (x.Element ref) => new DNList.fromRef(ref),
        (x.Node node, DaxeNode parent) => new DNList.fromNode(node, parent)
    );
    addDisplayType('liste',
        (x.Element ref) => new DNList.fromRef(ref),
        (x.Node node, DaxeNode parent) => new DNList.fromNode(node, parent)
    );
    addDisplayType('fichier',
        (x.Element ref) => new DNFile.fromRef(ref),
        (x.Node node, DaxeNode parent) => new DNFile.fromNode(node, parent)
    );
    addDisplayType('file',
        (x.Element ref) => new DNFile.fromRef(ref),
        (x.Node node, DaxeNode parent) => new DNFile.fromNode(node, parent)
    );
    addDisplayType('form',
        (x.Element ref) => new DNForm.fromRef(ref),
        (x.Node node, DaxeNode parent) => new DNForm.fromNode(node, parent)
    );
    addDisplayType('formulaire',
        (x.Element ref) => new DNForm.fromRef(ref),
        (x.Node node, DaxeNode parent) => new DNForm.fromNode(node, parent)
    );
    addDisplayType('hiddendiv',
        (x.Element ref) => new DNHiddenDiv.fromRef(ref),
        (x.Node node, DaxeNode parent) => new DNHiddenDiv.fromNode(node, parent)
    );
    addDisplayType('hiddenp',
        (x.Element ref) => new DNHiddenP.fromRef(ref),
        (x.Node node, DaxeNode parent) => new DNHiddenP.fromNode(node, parent)
    );
    addDisplayType('simpletype',
        (x.Element ref) => new DNSimpleType.fromRef(ref),
        (x.Node node, DaxeNode parent) => new DNSimpleType.fromNode(node, parent)
    );
    addDisplayType('string',
        (x.Element ref) => new DNString.fromRef(ref),
        (x.Node node, DaxeNode parent) => new DNString.fromNode(node, parent)
    );
    addDisplayType('style',
        (x.Element ref) => new DNStyle.fromRef(ref),
        (x.Node node, DaxeNode parent) => new DNStyle.fromNode(node, parent)
    );
    addDisplayType('stylespan',
        (x.Element ref) => new DNStyleSpan.fromRef(ref),
        (x.Node node, DaxeNode parent) => new DNStyleSpan.fromNode(node, parent)
    );
    addDisplayType('symbol2',
        (x.Element ref) => new DNSpecial.fromRef(ref),
        (x.Node node, DaxeNode parent) => new DNSpecial.fromNode(node, parent)
    );
    addDisplayType('symbole2',
        (x.Element ref) => new DNSpecial.fromRef(ref),
        (x.Node node, DaxeNode parent) => new DNSpecial.fromNode(node, parent)
    );
    addDisplayType('table',
        (x.Element ref) => new DNTable.fromRef(ref),
        (x.Node node, DaxeNode parent) => new DNTable.fromNode(node, parent)
    );
    addDisplayType('texttable',
        (x.Element ref) => new DNTable.fromRef(ref),
        (x.Node node, DaxeNode parent) => new DNTable.fromNode(node, parent)
    );
    addDisplayType('tabletexte',
        (x.Element ref) => new DNTable.fromRef(ref),
        (x.Node node, DaxeNode parent) => new DNTable.fromNode(node, parent)
    );
    addDisplayType('text',
        null,
        (x.Node node, DaxeNode parent) => new DNText.fromNode(node, parent)
    );
    addDisplayType('texte',
        null,
        (x.Node node, DaxeNode parent) => new DNText.fromNode(node, parent)
    );
    addDisplayType('typesimple',
        (x.Element ref) => new DNSimpleType.fromRef(ref),
        (x.Node node, DaxeNode parent) => new DNSimpleType.fromNode(node, parent)
    );
    addDisplayType('vide',
        (x.Element ref) => new DNEmpty.fromRef(ref),
        (x.Node node, DaxeNode parent) => new DNEmpty.fromNode(node, parent)
    );
    addDisplayType('zone',
        (x.Element ref) => new DNArea.fromRef(ref),
        (x.Node node, DaxeNode parent) => new DNArea.fromNode(node, parent)
    );
    addDisplayType('witem',
        (x.Element ref) => new DNWItem.fromRef(ref),
        (x.Node node, DaxeNode parent) => new DNWItem.fromNode(node, parent)
    );
    addDisplayType('wlist',
        (x.Element ref) => new DNWList.fromRef(ref),
        (x.Node node, DaxeNode parent) => new DNWList.fromNode(node, parent)
    );
  }
  
  static void addDisplayType(String displayType, ConstructorFromRef cref, ConstructorFromNode cnode) {
    if (cref != null)
      nodeFactory.constructorsFromRef[displayType] = cref;
    if (cnode != null)
      nodeFactory.constructorsFromNode[displayType] = cnode;
  }
  
  static DaxeNode createFromNode(x.Node n, DaxeNode parent) {
    x.Element ref;
    if (n is x.Document) {
      return(new DNDocument.fromNode(n));
    } else if (n is x.Comment) {
      return(new DNComment.fromNode(n, parent));
    } else if (n is x.ProcessingInstruction) {
      return(new DNProcessingInstruction.fromNode(n, parent));
    } else if (n is x.CDATASection) {
      return(new DNCData.fromNode(n, parent));
    }
    if (n is x.Element) {
      ref = doc.cfg.getElementRef(n, parent != null ? parent.ref : null);
    } else {
      ref = null;
    }
    String dt = doc.cfg.nodeDisplayType(ref, n.nodeName, n.nodeType);
    ConstructorFromNode cnode = nodeFactory.constructorsFromNode[dt];
    DaxeNode dn;
    if (cnode != null)
      dn = cnode(n, parent);
    else if (dt == 'plugin') {
      String className = doc.cfg.nodeParameterValue(ref, 'element', n.nodeName, "classe", null);
      if (className == 'xpages.JEEquationMemoire')
        dn = new DNEquationMem.fromNode(n, parent);
      else if (className == 'xpages.JEEquaTeXMemoire')
        dn = new DNEquaTexMem.fromNode(n, parent);
      else if (className == 'xpages.jeimage.JEImage')
        dn = new DNFile.fromNode(n, parent);
    } else
      dn = new DNString.fromNode(n, parent);
    return(dn);
  }
  
  static DaxeNode create(x.Element elementRef, [String nodeType = 'element']) {
    if (nodeType == 'commentaire') {
      return(new DNComment());
    } else if (nodeType == 'instruction') {
      return(new DNProcessingInstruction());
    } else if (nodeType == 'cdata') {
      return(new DNCData());
    }
    String dt = doc.cfg.nodeDisplayType(elementRef, doc.cfg.elementName(elementRef), x.Node.ELEMENT_NODE);
    ConstructorFromRef cref = nodeFactory.constructorsFromRef[dt];
    DaxeNode dn;
    if (cref != null)
      dn = cref(elementRef);
    else if (dt == 'plugin') {
      String className = doc.cfg.nodeParameterValue(elementRef, 'element', doc.cfg.elementName(elementRef), "classe", null);
      if (className == 'xpages.JEEquationMemoire')
        dn = new DNEquationMem.fromRef(elementRef);
      else if (className == 'xpages.JEEquaTeXMemoire')
        dn = new DNEquaTexMem.fromRef(elementRef);
      else if (className == 'xpages.jeimage.JEImage')
        dn = new DNFile.fromRef(elementRef);
    } else
      dn = new DNString.fromRef(elementRef);
    return(dn);
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/node_offset_position.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/node_offset_position.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

/**
 * A position in the XML document, created with a parent node and offset within this node.
 */
class NodeOffsetPosition implements Position {
  DaxeNode _dn;
  int _dnOffset; // offset within _dn (a child counts for 1 offset)
  // for text nodes, _dnOffset is the offset within the string
  
  NodeOffsetPosition(DaxeNode node, int offset) {
    assert(node != null);
    assert(offset >= 0);
    _dn = node;
    _dnOffset = offset;
  }
  
  NodeOffsetPosition.fromLeftOffsetPosition(LeftOffsetPosition lopos) {
    _dn = doc.dndoc;
    _dnOffset = 0;
    move(lopos.leftOffset);
  }
  
  NodeOffsetPosition.fromRightOffsetPosition(RightOffsetPosition ropos) {
    _dn = doc.dndoc;
    _dnOffset = _dn.offsetLength;
    move(-ropos.rightOffset);
  }
  
  NodeOffsetPosition.clone(Position pos) {
    _dn = pos.dn;
    _dnOffset = pos.dnOffset;
  }
  
  DaxeNode get dn {
    return(_dn);
  }
  
  int get dnOffset {
    return(_dnOffset);
  }
  
  int get leftOffset {
    LeftOffsetPosition lopos = new LeftOffsetPosition.fromNodeOffsetPosition(this);
    return(lopos.leftOffset);
  }
  
  int get rightOffset {
    RightOffsetPosition ropos = new RightOffsetPosition.fromNodeOffsetPosition(this);
    return(ropos.rightOffset);
  }
  
  bool operator ==(Position other) {
    NodeOffsetPosition nopos;
    if (other is NodeOffsetPosition)
      nopos = other;
    else if (other is LeftOffsetPosition)
      nopos = new NodeOffsetPosition.fromLeftOffsetPosition(other);
    else if (other is RightOffsetPosition)
      nopos = new NodeOffsetPosition.fromRightOffsetPosition(other);
    return(_dn == nopos._dn && _dnOffset == nopos._dnOffset);
  }
  
  bool operator <(Position other) {
    NodeOffsetPosition nopos;
    if (other is NodeOffsetPosition)
      nopos = other;
    else if (other is LeftOffsetPosition)
      nopos = new NodeOffsetPosition.fromLeftOffsetPosition(other);
    else if (other is RightOffsetPosition)
      nopos = new NodeOffsetPosition.fromRightOffsetPosition(other);
    DaxeNode cp = null;
    DaxeNode p1 = _dn;
    double offset1 = _dnOffset.toDouble();
    while (p1 != null) {
      DaxeNode p2 = nopos._dn;
      double offset2 = nopos._dnOffset.toDouble();
      while (p2 != null) {
        if (p1 == p2) {
          return(offset1 < offset2);
        }
        if (p2.parent == null)
          break;
        offset2 = p2.parent.offsetOf(p2) + 0.5;
        p2 = p2.parent;
      }
      if (p1.parent == null)
        break;
      offset1 = p1.parent.offsetOf(p1) + 0.5;
      p1 = p1.parent;
    }
    assert(false); // no common parent ???
    return(false);
  }
  
  bool operator <=(Position other) {
    return(this < other || this == other);
  }
  
  bool operator >(Position other) {
    return(!(this == other || this < other));
  }
  
  bool operator >=(Position other) {
    return(this > other || this == other);
  }
  
  void move(int offset) {
    if (offset > 0) {
      int n = offset;
      while (n > 0) {
        if (_dnOffset == _dn.offsetLength) {
          _dnOffset = _dn.parent.offsetOf(_dn) + 1;
          _dn = _dn.parent;
        } else if (_dn is DNText) {
          _dnOffset++;
        } else {
          _dn = _dn.childAtOffset(_dnOffset);
          _dnOffset = 0;
        }
        n--;
      }
    } else if (offset < 0) {
      int n = offset;
      while (n < 0) {
        if (_dnOffset == 0) {
          _dnOffset = _dn.parent.offsetOf(_dn);
          _dn = _dn.parent;
        } else if (_dn is DNText) {
          _dnOffset--;
        } else {
          _dn = _dn.childAtOffset(_dnOffset-1);
          _dnOffset = _dn.offsetLength;
        }
        n++;
      }
    }
  }
  
  void moveInsideTextNodeIfPossible() {
    if (_dn.nodeType == DaxeNode.ELEMENT_NODE && _dnOffset > 0 &&
        _dn.childAtOffset(_dnOffset - 1).nodeType == DaxeNode.TEXT_NODE) {
      _dn = _dn.childAtOffset(_dnOffset - 1);
      _dnOffset = _dn.offsetLength;
    } else if (_dn.nodeType == DaxeNode.ELEMENT_NODE &&
        _dnOffset < _dn.offsetLength &&
        _dn.childAtOffset(_dnOffset).nodeType == DaxeNode.TEXT_NODE) {
      _dn = _dn.childAtOffset(_dnOffset);
      _dnOffset = 0;
    } else if (_dnOffset == 0 && _dn.firstChild != null &&
        _dn.firstChild.nodeType == DaxeNode.TEXT_NODE) {
      _dn = _dn.firstChild;
    }
  }
  
  /**
   * offset top-left coordinates for the position
   */
  Point positionOnScreen() {
    if (_dn.nodeType == DaxeNode.TEXT_NODE) {
      h.Element hn = _dn.getHTMLContentsNode();
      if (hn == null || hn.nodes.length == 0)
        return(null);
      assert(hn.nodes.first is h.Text);
      h.Text n = hn.nodes.first;
      int offset = _dnOffset; // TODO:fix for !next
      String s = n.text;
      assert(s.length != 0);
      /*
      h.Text n2 = new h.Text(s.substring(offset));
      if (s.length == offset)
        n2.text = "|";
      n.text = s.substring(0, offset);
      h.SpanElement spos = new h.SpanElement();
      spos.append(n2);
      if (n.nextNode == null)
        n.parent.append(spos);
      else
        n.parent.insertBefore(spos, n.nextNode);
      h.Rect r = spos.getClientRects()[0];
      Point pt = new Point(r.left, r.top);
      spos.remove();
      */
      // changing the text might induce
      // layout changes which move the position (for instance in a table cell)
      // -> we add a span on the text after the cursor
      /*
      Point pt;
      if (offset != 0) {
        n.text = s.substring(0, offset);
        h.SpanElement spos = new h.SpanElement();
        spos.appendText(s.substring(offset));
        if (n.nextNode == null)
          n.parent.append(spos);
        else
          n.parent.insertBefore(spos, n.nextNode);
        h.Rect r = spos.getClientRects()[0];
        pt = new Point(r.left, r.top);
        spos.remove();
        n.text = s;
      } else {
        h.Rect r = n.parent.getClientRects()[0];
        pt = new Point(r.left, r.top);
      }
      // new problem: wrong position when the cursor is before an hyphen inside
      // a broken word at the end of the line
      */
      // trying with Range...
      // this does not work in the case of an element following a line breaking space
      h.Range range = new h.Range();
      Point pt;
      if (offset == 0) {
        range.setStart(n, offset);
        range.setEnd(n, s.length);
        h.Rectangle r = range.getClientRects().first;
        pt = new Point(r.left, r.top);
      } else if (s[offset-1] == '\n' || s[offset-1] == ' ') {
        // to the right of a \n or a space
        if (offset == s.length) {
          // ranges always report wrong positions in this case :(
          if (_dn.nextSibling != null && _dn.nextSibling.nodeType == DaxeNode.ELEMENT_NODE &&
              (s[offset-1] == '\n' || !_dn.nextSibling.block)) {
            h.Rectangle r = (_dn.nextSibling.getHTMLNode()).getClientRects()[0];
            pt = new Point(r.left, r.top);
          } else if (s[offset-1] == ' ') {
            range.setStart(n, 0);
            range.setEnd(n, offset);
            h.Rectangle r = range.getClientRects().last;
            pt = new Point(r.right, r.top);
          } else {
            // FIXME: adding a span with text can change a table layout with Firefox, causing wrong results
            h.SpanElement spos = new h.SpanElement();
            spos.appendText("|");
            if (n.nextNode == null)
              hn.append(spos);
            else
              hn.insertBefore(spos, n.nextNode);
            h.Rectangle r = spos.getClientRects()[0];
            pt = new Point(r.left, r.top);
            spos.remove();
          }
        } else {
          range.setStart(n, offset);
          range.setEnd(n, offset + 1);
          List<h.Rectangle> rects = range.getClientRects();
          h.Rectangle r;
          if (s[offset-1] == ' ' && offset < s.length && s[offset] == '\n') {
            if (h.window.navigator.appVersion.contains("Trident")) { // IE11, TODO: test other IE versions
              // ranges do not work in this case with IE11, we will have to add pixels from the previous position
              range.setStart(n, offset - 1);
              range.setEnd(n, offset);
              rects = range.getClientRects();
              r = rects.first;
              r = new h.Rectangle(r.left + 7, r.top, r.width, r.height); // 7px added for the space
            } else
              r = rects.first;
          } else if (s[offset] == '\n' && rects.length == 3)
            r = rects[1];
          else if ((h.window.navigator.userAgent.toLowerCase().indexOf('msie') >= 0 ||
              h.window.navigator.appVersion.contains("Trident")) &&
              s[offset-1] == '\n' && s[offset] == '\n' && rects.length == 2) // IE
            r = rects.first;
          else {
            // preferably use a Rectangle with a width > 1 (useful in the case of 1\n2\n with Chromium)
            r = rects.last;
            for (h.Rectangle ri in rects)
              if (ri.width > 1) {
                r = ri;
                break;
              }
          }
          pt = new Point(r.left, r.top);
        }
      } else {
        h.Rectangle r;
        if (h.window.navigator.appVersion.contains("Trident") && offset < s.length && s[offset] == '\n') {
          // IE11 is crazy: it would returns the position *after* the \n as the last rect
          range.setStart(n, offset-1);
          range.setEnd(n, offset);
          r = range.getClientRects().first;
        } else {
          range.setStart(n, 0);
          range.setEnd(n, offset);
          r = range.getClientRects().last;
        }
        pt = new Point(r.right, r.top);
      }
      return(pt);
      
    } else { // not in a text node:
      List<DaxeNode> children = _dn.childNodes;
      if (children != null && _dnOffset > 0 && _dnOffset == children.length) {
        // at the end of the children
        h.Element n = children[_dnOffset-1].getHTMLNode();
        if (n == null)
          return(null);
        h.Rectangle r;
        if (n is h.ImageElement || n is h.TableRowElement) {
          r = n.getBoundingClientRect();
          return(new Point(r.right, r.top));
        } else if (n is h.DivElement || n is h.TableElement || n is h.UListElement || n is h.LIElement) {
          r = n.getBoundingClientRect();
          return(new Point(r.left, r.bottom));
        } else {
          /*
          // FIXME: adding a span with text can change a table layout with Firefox, causing wrong results
          h.SpanElement spos = new h.SpanElement();
          spos.append(new h.Text("|"));
          n.append(spos);
          List<h.Rectangle> rects = spos.getClientRects();
          if (rects.length > 0)
            r = rects[0];
          else
            r = null;
          spos.remove();
          if (r == null)
            return(null);
          return(new Point(r.left, r.top));
          */
          // this seems to work (top right corner of the last rect of the last child's HTML):
          List<h.Rectangle> rects = n.getClientRects();
          if (rects.length == 0)
            return(null);
          h.Rectangle r = rects.last;
          return(new Point(r.right, r.top));
        }
      } else if (children != null && _dnOffset < children.length) {
        // within the children
        h.Element hn = children[_dnOffset].getHTMLNode();
        if (hn == null)
          return(null);
        if (_dnOffset > 0) {
          // between two nodes
          DaxeNode dn1 = children[_dnOffset - 1];
          DaxeNode dn2 = children[_dnOffset];
          h.Element hn1 = dn1.getHTMLNode();
          h.Element hn2 = hn;
          if (dn1.block && !dn2.block) {
            // block-inline
            List<h.Rectangle> rects2 = hn2.getClientRects();
            if (rects2.length == 0 )
              return(null);
            h.Rectangle r2 = rects2.first;
            return(new Point(r2.left, r2.top));
          } else if (dn1.block && dn2.block) {
            // block-block
            h.Rectangle r1 = hn1.getBoundingClientRect();
            h.Rectangle r2 = hn2.getBoundingClientRect();
            return(new Point(r2.left, (r1.bottom + r2.top)/2));
          } else {
            // inline-inline or inline-block
            List<h.Rectangle> rects1 = hn1.getClientRects();
            if (rects1.length == 0 )
              return(null);
            h.Rectangle r1 = rects1.last;
            return(new Point(r1.right, r1.top));
          }
        }
        // before the first node
        if (children[_dnOffset] is DNWItem) {
          // special case for the first li in a WYSIWYG list
          h.Rectangle r = hn.getClientRects()[0];
          return(new Point(r.left - 21, r.top + 2));
        }
        h.Rectangle r = hn.getClientRects()[0];
        return(new Point(r.left, r.top));
      } else {
        // no child inside _dn
        assert(_dnOffset == 0);
        h.Element hn = _dn.getHTMLContentsNode();
        if (hn == null)
          return(null);
        List<h.Rectangle> rects = hn.getClientRects();
        if (rects.length == 0)
          return(null);
        h.Rectangle r = rects[0];
        return(new Point(r.left, r.top));
      }
    }
  }
  
  String xPath({bool titles:false}) {
    String s = "";
    DaxeNode n = _dn;
    while (n != null) {
      String spos = "";
      if (n.parent != null) {
        int position = 1;
        for (DaxeNode n2 = n.parent.firstChild; n2 != null; n2 = n2.nextSibling) {
          if (n2 == n)
            break;
          if (n2.nodeType == DaxeNode.ELEMENT_NODE && n2.nodeName == n.nodeName)
            position++;
        }
        spos = "[$position]";
      }
      if (n.nodeType == DaxeNode.ELEMENT_NODE) {
        String title;
        if (titles && doc.cfg != null && n.ref != null)
          title = doc.cfg.elementTitle(n.ref);
        else
          title = n.nodeName;
        s = "$title$spos/$s";
      } else if (n.nodeType == DaxeNode.TEXT_NODE)
        s = "#text";
      n = n.parent;
    }
    return("/$s");
  }
  
  String toString() {
    return("[NodeOffsetPosition ${_dn.nodeName} ${_dnOffset}]");
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/position.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/position.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

/**
 * A position in the XML document.
 */
abstract class Position {
  
  factory Position(DaxeNode node, int offset) {
    return(new NodeOffsetPosition(node, offset));
  }
  
  factory Position.fromLeft(int leftOffset) {
    return(new LeftOffsetPosition(leftOffset));
  }
  
  factory Position.fromRight(int rightOffset) {
    return(new RightOffsetPosition(rightOffset));
  }
  
  factory Position.nodeOffsetPosition(Position pos) {
    if (pos is NodeOffsetPosition)
      return(new NodeOffsetPosition.clone(pos));
    else if (pos is LeftOffsetPosition)
      return(new NodeOffsetPosition.fromLeftOffsetPosition(pos));
    else if (pos is RightOffsetPosition)
      return(new NodeOffsetPosition.fromRightOffsetPosition(pos));
  }
  
  factory Position.leftOffsetPosition(Position pos) {
    if (pos is NodeOffsetPosition)
      return(new LeftOffsetPosition.fromNodeOffsetPosition(pos));
    else if (pos is LeftOffsetPosition)
      return(new LeftOffsetPosition.clone(pos));
    else if (pos is RightOffsetPosition)
      return(new LeftOffsetPosition.fromRightOffsetPosition(pos));
  }
  
  factory Position.rightOffsetPosition(Position pos) {
    if (pos is NodeOffsetPosition)
      return(new RightOffsetPosition.fromNodeOffsetPosition(pos));
    else if (pos is LeftOffsetPosition)
      return(new RightOffsetPosition.fromLeftOffsetPosition(pos));
    else if (pos is RightOffsetPosition)
      return(new RightOffsetPosition.clone(pos));
  }
  
  factory Position.clone(Position pos) {
    if (pos is NodeOffsetPosition)
      return(new NodeOffsetPosition(pos.dn, pos.dnOffset));
    else if (pos is LeftOffsetPosition)
      return(new LeftOffsetPosition(pos.leftOffset));
    else if (pos is RightOffsetPosition)
      return(new RightOffsetPosition(pos.rightOffset));
  }
  
  /**
   * Returns the parent node for this position.
   */
  DaxeNode get dn;
  
  /**
   * Returns the offset within the parent node.
   */
  int get dnOffset;
  
  /**
   * Returns the left offset in the document.
   */
  int get leftOffset;
  
  /**
   * Returns the right offset in the document.
   */
  int get rightOffset;
  
  bool operator ==(Position other);
  
  bool operator <(Position other);
  
  bool operator <=(Position other);
  
  bool operator >(Position other);
  
  bool operator >=(Position other);
  
  /**
   * Moves the position right by the offset, counting movements to enter or exit a text node.
   */
  void move(int offset);
  
  void moveInsideTextNodeIfPossible();
  
  /**
   * offset top-left coordinates for the position
   */
  Point positionOnScreen();
  
  String xPath({bool titles:false});
  
  String toString();
}

/**
 * A point defined by x,y coordinates.
 * The coordinates can be integers or doubles.
 */
class Point {
  num x, y;
  Point(num this.x, num this.y);
}

Index: modules/damieng/graphical_editor/daxe/lib/src/right_offset_position.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/right_offset_position.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

/**
 * A position in the XML document based on the document offset from the end.
 */
class RightOffsetPosition implements Position {
  int _rightOffset;
  
  RightOffsetPosition(int rightOffset) {
    assert(rightOffset >= 0);
    _rightOffset = rightOffset;
  }
  
  RightOffsetPosition.fromNodeOffsetPosition(NodeOffsetPosition nopos) {
    DaxeNode targetDn = nopos.dn;
    int targetDnOffset = nopos.dnOffset;
    _rightOffset = 0;
    DaxeNode dn = doc.dndoc;
    int dnOffset = dn.offsetLength;
    while (dn != targetDn || dnOffset != targetDnOffset) {
      if (dnOffset == 0) {
        dnOffset = dn.parent.offsetOf(dn);
        dn = dn.parent;
      } else if (dn is DNText) {
        dnOffset--;
      } else {
        dn = dn.childAtOffset(dnOffset-1);
        dnOffset = dn.offsetLength;
      }
      _rightOffset++;
    }
  }
  
  RightOffsetPosition.fromLeftOffsetPosition(LeftOffsetPosition lopos) {
    DaxeNode targetDn = doc.dndoc;
    int targetDnOffset = targetDn.offsetLength;
    int offset = 0;
    DaxeNode dn = doc.dndoc;
    int dnOffset = 0;
    while (dn != targetDn || dnOffset != targetDnOffset) {
      if (dnOffset == dn.offsetLength) {
        dnOffset = dn.parent.offsetOf(dn) + 1;
        dn = dn.parent;
      } else if (dn is DNText) {
        dnOffset++;
      } else {
        dn = dn.childAtOffset(dnOffset);
        dnOffset = 0;
      }
      offset++;
    }
    _rightOffset = offset - lopos.leftOffset;
  }
  
  RightOffsetPosition.clone(RightOffsetPosition pos) {
    _rightOffset = pos.rightOffset;
  }
  
  DaxeNode get dn {
    NodeOffsetPosition nopos = new NodeOffsetPosition.fromRightOffsetPosition(this);
    return(nopos.dn);
  }
  
  int get dnOffset {
    NodeOffsetPosition nopos = new NodeOffsetPosition.fromRightOffsetPosition(this);
    return(nopos.dnOffset);
  }
  
  int get leftOffset {
    LeftOffsetPosition lopos = new LeftOffsetPosition.fromRightOffsetPosition(this);
    return(lopos.leftOffset);
  }
  
  int get rightOffset {
    return(_rightOffset);
  }
  
  bool operator ==(Position other) {
    return(_rightOffset == other.rightOffset);
  }
  
  bool operator <(Position other) {
    return(_rightOffset > other.rightOffset);
  }
  
  bool operator <=(Position other) {
    return(_rightOffset >= other.rightOffset);
  }
  
  bool operator >(Position other) {
    return(_rightOffset < other.rightOffset);
  }
  
  bool operator >=(Position other) {
    return(_rightOffset <= other.rightOffset);
  }
  
  void move(int offset) {
    _rightOffset -= offset;
  }
  
  void moveInsideTextNodeIfPossible() {
    NodeOffsetPosition nopos = new NodeOffsetPosition.fromRightOffsetPosition(this);
    nopos.moveInsideTextNodeIfPossible();
    _rightOffset = (new RightOffsetPosition.fromNodeOffsetPosition(nopos))._rightOffset;
  }
  
  Point positionOnScreen() {
    NodeOffsetPosition nopos = new NodeOffsetPosition.fromRightOffsetPosition(this);
    return(nopos.positionOnScreen());
  }
  
  String xPath({bool titles:false}) {
    NodeOffsetPosition nopos = new NodeOffsetPosition.fromRightOffsetPosition(this);
    return(nopos.xPath(titles:titles));
  }
  
  String toString() {
    return("[RightOffsetPosition $_rightOffset]");
  }
  
}
Index: modules/damieng/graphical_editor/daxe/lib/src/simple_schema.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/simple_schema.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

library SimpleSchema;

import 'xmldom/xmldom.dart';
import 'interface_schema.dart';
import 'dart:collection';


/**
 * Class implementing InterfaceSchema for Jaxe's simple schemas.
 */
class SimpleSchema implements InterfaceSchema {
  HashMap<String, String> _hashTitles;
  Element _schemaRoot; // config file root element
  HashMap<String, Element> _elementDefCache; // cache for associations element name -> definition
  HashMap<Element, String> _elementNamesCache; // cache for associations definition -> element name
  
  SimpleSchema(Element schemaRoot, HashMap<String, String> hashElementTitles) {
    _schemaRoot = schemaRoot;
    _hashTitles = hashElementTitles;
    _buildElementDefCache();
  }
  
  bool elementInSchema(final Element elementRef) {
    Document domdoc = elementRef.ownerDocument;
    return(domdoc == _schemaRoot.ownerDocument);
  }
  
  Element elementReferenceByName(final String name) {
    return(_elementDefCache[name]);
  }
  
  List<Element> elementReferencesByName(final String name) {
    return([_elementDefCache[name]]);
  }
  
  Element elementReference(final Element el, final Element parentRef) {
    String name;
    if (el.prefix == null)
      name = el.nodeName;
    else
      name = el.localName;
    return(elementReferenceByName(name));
  }
  
  String elementName(final Element elementRef) {
    return(_elementNamesCache[elementRef]);
  }
  
  String elementNamespace(final Element elementRef) {
    return(null);
  }
  
  String elementPrefix(final Element elementRef) {
    return(null);
  }
  
  String elementDocumentation(final Element elementRef) {
    return(null);
  }
  
  List<String> elementValues(final Element elementRef) {
    return(null);
  }
  
  List<String> suggestedElementValues(final Element elementRef) {
    return(null);
  }
  
  bool elementValueIsValid(final Element elementRef, final String value) {
    return(true);
  }
  
  List<String> namespaceList() {
    return(null);
  }
  
  bool hasNamespace(final String namespace) {
    return(namespace == null);
  }
  
  String namespacePrefix(final String ns) {
    return(null);
  }
  
  String getTargetNamespace() {
    return(null);
  }
  
  List<Element> elementsOutsideNamespace(final String namespace) {
    if (namespace == null)
      return(new List<Element>());
    else
      return(allElements());
  }
  
  List<Element> elementsWithinNamespaces(final Set<String> namespaces) {
    return(new List<Element>());
  }
  
  List<Element> allElements() {
    return(new List.from(_elementNamesCache.keys));
  }
  
  List<Element> rootElements() {
    return(allElements());
  }
  
  bool requiredElement(final Element parentRef, final Element childRef) {
    return(false);
  }
  
  bool multipleChildren(final Element parentRef, final Element childRef) {
    return(true);
  }
  
  List<Element> subElements(final Element parentRef) {
    final List<Element> liste = new List<Element>();
    final List<Node> lsousel = parentRef.getElementsByTagName('SOUS-ELEMENT');
    for (int i=0; i<lsousel.length; i++) {
      final Element sousel = lsousel[i] as Element;
      liste.add(_elementDefCache[sousel.getAttribute('element')]);
    }
    final List<Node> lsousens = parentRef.getElementsByTagName('SOUS-ENSEMBLE');
    for (int i=0; i<lsousens.length; i++) {
      final Element sousens = lsousens[i] as Element;
      final String nomens = sousens.getAttribute('ensemble');
      final List<Node> lens = _schemaRoot.getElementsByTagName('ENSEMBLE');
      for (int j=0; j<lens.length; j++) {
        final Element ensemble = lens[j] as Element;
        if (nomens == ensemble.getAttribute('nom'))
          liste.addAll(subElements(ensemble));
      }
    }
    return(liste);
  }
  
  String regularExpression(final Element parentRef, final bool modevisu, final bool modevalid) {
    final List<Element> lsousb = subElements(parentRef);
    final StringBuffer expr = new StringBuffer();
    final int s = lsousb.length;
    for (int i=0; i < s; i++) {
      if (i != 0)
        expr.write('|');
      if (modevisu)
        expr.write(_elementTitle(lsousb[i]));
      else
        expr.write(elementName(lsousb[i]));
      if (!modevisu)
        expr.write(',');
    }
    if (s != 0) {
      //expr.insert(0, '(');
      String s = expr.toString();
      expr.clear();
      expr.write('(');
      expr.write(s);
      expr.write(')*');
    }
    return(expr.toString());
  }
  
  List<Element> parentElements(final Element elementRef) {
    final List<Element> liste = new List<Element>();
    if (elementRef.nodeName == 'ELEMENT') {
      final List<Node> lsousel = _schemaRoot.getElementsByTagName('SOUS-ELEMENT');
      for (int i=0; i<lsousel.length; i++) {
        final Element sousel = lsousel[i] as Element;
        if (sousel.getAttribute('element') == elementRef.getAttribute('nom')) {
          final Element parent = sousel.parentNode as Element;
          if (parent.nodeName == 'ELEMENT')
            liste.add(parent);
          else if (parent.nodeName == 'ENSEMBLE')
            liste.addAll(parentElements(parent));
        }
      }
    } else if (elementRef.nodeName == 'ENSEMBLE') {
      final String nomens = elementRef.getAttribute('nom');
      final List<Node> lsousens = _schemaRoot.getElementsByTagName('SOUS-ENSEMBLE');
      for (int i=0; i<lsousens.length; i++) {
        final Element sousens = lsousens[i] as Element;
        if (sousens.getAttribute('ensemble') == nomens) {
          final Element parent = sousens.parentNode as Element;
          if (parent.nodeName == 'ELEMENT')
            liste.add(parent);
          else if (parent.nodeName == 'ENSEMBLE')
            liste.addAll(parentElements(parent));
        }
      }
    }
    return(liste);
  }
  
  List<Element> elementAttributes(final Element elementRef) {
    final List<Node> latt = elementRef.getElementsByTagName('ATTRIBUT');
    final List<Element> l = new List<Element>();
    for (Node n in latt)
      l.add(n as Element);
    return(l);
  }
  
  String attributeName(final Element attributeRef) {
    return(attributeRef.getAttribute('nom'));
  }
  
  String attributeNamespace(final Element attributeRef) {
    return(null);
  }
  
  String attributeDocumentation(final Element attributeRef) {
    return(null);
  }
  
  String attributeNamespaceByName(final String attributeName) {
    return(null);
  }
  
  bool attributeIsRequired(final Element refParent, final Element attributeRef) {
    final String presence = attributeRef.getAttribute('presence');
    return(presence == 'obligatoire');
  }
  
  List<String> attributeValues(final Element attributeRef) {
    final List<Node> lval = attributeRef.getElementsByTagName('VALEUR');
    if (lval.length == 0)
      return(null);
    final List<String> liste = new List<String>();
    for (int i=0; i<lval.length; i++) {
      final Element val = lval[i] as Element;
      final String sval = val.firstChild.nodeValue.trim();
      liste.add(sval);
    }
    return(liste);
  }
  
  List<String> suggestedAttributeValues(final Element attributeRef) {
    return(null);
  }
  
  String defaultAttributeValue(final Element attributeRef) {
    return(null);
  }
  
  attributeIsValid(final Element attributeRef, final String value) {
    final String presence = attributeRef.getAttribute('presence');
    bool required = (presence == 'obligatoire');
    if ((value == null || value == '') && required)
      return(false);
    final List<String> valeurs = attributeValues(attributeRef);
    if (valeurs != null)
      return(valeurs.contains(value));
    return(true);
  }
  
  Element attributeParent(final Element attributeRef) {
    return(attributeRef.parentNode as Element);
  }
  
  bool canContainText(final Element elementRef) {
    final String texte  = elementRef.getAttribute('texte');
    return(texte == 'autorise');
  }
  
  
  /**
   * Builds a cache for element definitions and names
   */
  void _buildElementDefCache() {
    _elementDefCache = new HashMap<String, Element>();
    _elementNamesCache = new HashMap<Element, String>();
    final List<Node> lelements = _schemaRoot.getElementsByTagName('ELEMENT');
    for (int i=0; i<lelements.length; i++) {
      final Element el = lelements[i] as Element;
      final String nom = el.getAttribute('nom');
      _elementDefCache[nom] = el;
      _elementNamesCache[el] = nom;
    }
  }
  
  String _elementTitle(final Element elementRef) {
    String name = _elementNamesCache[elementRef];
    if (_hashTitles[name] != null)
      return(_hashTitles[name]);
    else
      return(name);
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/source_window.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/source_window.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

/**
 * Displays the XML source code in a window.
 */
class SourceWindow {
  
  void show() {
    x.Document domdoc = doc.toDOMDoc();
    doc.indentDOMDocument(domdoc);
    
    h.DivElement div1 = new h.DivElement();
    div1.id = 'dlg1';
    div1.classes.add('dlg1');
    
    h.DivElement divWindow = new h.DivElement();
    divWindow.classes.add('source_window');
    
    h.DivElement divContent = new h.DivElement();
    divContent.classes.add('source_content');
    addNode(domdoc, divContent);
    divWindow.append(divContent);
    
    h.DivElement divBottom = new h.DivElement();
    divBottom.classes.add('source_bottom');
    h.ButtonElement bSelect = new h.ButtonElement();
    bSelect.attributes['type'] = 'button';
    bSelect.appendText(Strings.get("source.select_all"));
    bSelect.onClick.listen((h.MouseEvent event) => selectAll());
    divBottom.append(bSelect);
    h.ButtonElement bOk = new h.ButtonElement();
    bOk.attributes['type'] = 'submit';
    bOk.appendText(Strings.get("button.Close"));
    bOk.onClick.listen((h.MouseEvent event) => close());
    divBottom.append(bOk);
    divWindow.append(divBottom);
    
    div1.append(divWindow);
    
    h.document.body.append(div1);
    bOk.focus();
  }
  
  void addNode(x.Node node, h.Element html) {
    switch (node.nodeType) {
      case x.Node.DOCUMENT_NODE :
        h.SpanElement xmldec = new h.SpanElement();
        xmldec.classes.add('source_pi');
        String xmlVersion = (node as x.Document).xmlVersion;
        String xmlEncoding = (node as x.Document).xmlEncoding;
        xmldec.appendText('<?xml version="$xmlVersion" encoding="$xmlEncoding"?>');
        html.append(xmldec);
        html.appendText('\n');
        for (x.Node n=node.firstChild; n != null; n=n.nextSibling) {
          addNode(n, html);
        }
        break;
        
      case x.Node.ELEMENT_NODE :
        html.appendText('<');
        h.SpanElement elementName = new h.SpanElement();
        elementName.classes.add('source_element_name');
        elementName.appendText(node.nodeName);
        html.append(elementName);
        if (node.attributes != null) {
          for (x.Attr att in node.attributes.values) {
            html.appendText(' ');
            h.SpanElement attributeName = new h.SpanElement();
            attributeName.classes.add('source_attribute_name');
            attributeName.appendText(att.nodeName);
            html.append(attributeName);
            html.appendText('="');
            h.SpanElement attributeValue = new h.SpanElement();
            attributeValue.classes.add('source_attribute_value');
            _appendTextWithEntities(attributeValue, att.nodeValue);
            html.append(attributeValue);
            html.appendText('"');
          }
        }
        if (node.firstChild != null) {
          html.appendText('>');
          if (node.childNodes != null) {
            for (x.Node n in node.childNodes) {
              addNode(n, html);
            }
          }
          html.appendText('</');
          elementName = new h.SpanElement();
          elementName.classes.add('source_element_name');
          elementName.appendText(node.nodeName);
          html.append(elementName);
          html.appendText('>');
        } else {
          html.appendText('/>');
        }
        break;
        
      case x.Node.TEXT_NODE :
        _appendTextWithEntities(html, node.nodeValue);
        break;
      
      case x.Node.COMMENT_NODE :
        h.SpanElement span = new h.SpanElement();
        span.classes.add('source_comment');
        span.appendText(node.toString());
        html.append(span);
        break;
      
      case x.Node.ENTITY_REFERENCE_NODE :
        h.SpanElement span = new h.SpanElement();
        span.classes.add('source_entity');
        span.appendText(node.toString());
        html.append(span);
        break;
      
      case x.Node.CDATA_SECTION_NODE :
        h.SpanElement span = new h.SpanElement();
        span.classes.add('source_cdata');
        span.appendText(node.toString());
        html.append(span);
        break;
      
      case x.Node.PROCESSING_INSTRUCTION_NODE :
        h.SpanElement span = new h.SpanElement();
        span.classes.add('source_pi');
        span.appendText(node.toString());
        html.append(span);
        break;
        
      case x.Node.DOCUMENT_TYPE_NODE :
        h.SpanElement span = new h.SpanElement();
        span.classes.add('source_doctype');
        span.appendText(node.toString());
        html.append(span);
        break;
      
      default :
        html.appendText(node.toString());
        break;
    }
  }
  
  void _appendTextWithEntities(h.Element html, String s) {
    Map<String, String> cent = <String, String>{
      '&' : '&',
      '"' : '"',
      '<' : '<',
      '>' : '>'
    };
    Iterable<String> keys = cent.keys;
    int p = 0;
    while (p < s.length) {
      String c = s[p];
      if (keys.contains(c)) {
        if (p > 0)
          html.appendText(s.substring(0, p));
        h.SpanElement entity = new h.SpanElement();
        entity.classes.add('source_entity');
        entity.appendText(cent[c]);
        html.append(entity);
        s = s.substring(p + 1);
        p = 0;
      } else
        p++;
    }
    if (s.length > 0)
      html.appendText(s);
  }
  
  void close() {
    h.DivElement div1 = h.document.getElementById('dlg1');
    div1.remove();
    page.focusCursor();
  }
  
  void selectAll() {
    h.Selection selection = h.window.getSelection();
    h.Range r = new h.Range();
    r.selectNodeContents(h.querySelector('.source_content'));
    selection.removeAllRanges();
    selection.addRange(r);
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/strings.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/strings.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

/// Provides localized strings.
library Strings;

import 'dart:async';
import 'dart:collection';
import 'dart:html' as h;
import 'package:intl/intl_browser.dart'; // or intl-standalone (see findSystemLocale)


/**
 * Provides localized strings read from properties files.
 * The current language file is read at application loading time.
 */
class Strings {
  
  static String resourcePath = "packages/daxe/LocalStrings";
  static HashMap<String, String> map = null;
  static String systemLocale;
  static const defaultLocale = 'en';
  
  static Future<bool> load() {
    Completer<bool> completer = new Completer<bool>();
    findSystemLocale().then((String sl) {
      // note: this is not always the language chosen by the user
      // see https://code.google.com/p/chromium/issues/detail?id=101138
      if (sl != null)
        systemLocale = sl;
      else
        systemLocale = defaultLocale;
      String language = systemLocale.split('_')[0];
      String fullFilePath = "${resourcePath}_$language.properties";
      
      h.HttpRequest request = new h.HttpRequest();
      request.open("GET", fullFilePath);
      request.onLoad.listen((h.ProgressEvent event) {
        String txt = request.responseText;
        map = new HashMap<String, String>();
        List<String> lines = txt.split("\n");
        for (String line in lines) {
          if (line.startsWith('#'))
            continue;
          int ind = line.indexOf('=');
          if (ind == -1)
            continue;
          String key = line.substring(0, ind).trim();
          String value = line.substring(ind + 1).trim();
          map[key] = value;
        }
        completer.complete(true);
      });
      request.onError.listen((h.ProgressEvent event) {
        completer.completeError("Error when reading the strings in $resourcePath");
      });
      request.send();
    });
    return(completer.future);
  }
  
  static String get(String key) {
    return(map[key]);
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/tag.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/tag.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

/**
 * The graphical representation of a tag in the web page.
 */
class Tag {
  static const int START = 0;
  static const int END = 1;
  static const int EMPTY = 2;
  DaxeNode _dn;
  int _type;
  bool _long;
  
  Tag(DaxeNode dn, int type, [bool long]) {
    _dn = dn;
    _type = type;
    if (long != null)
      _long = long;
    else
      _long = false;
  }
  
  h.Element html() {
    h.Element span = new h.SpanElement();
    String classe;
    if (_type == START)
      classe = "start_tag";
    else if (_type == END)
      classe = "end_tag";
    else if (_type == EMPTY)
      classe = "empty_tag";
    span.classes.add(classe);
    if (_long)
      span.classes.add('long');
    if (_type != END) {
      bool hasAttributes;
      if (_dn.ref != null) {
        List<x.Element> attRefs = doc.cfg.elementAttributes(_dn.ref);
        hasAttributes = (attRefs != null && attRefs.length > 0);
      } else {
        hasAttributes = (_dn is! DNComment && _dn is! DNCData);
      }
      if (hasAttributes) {
        h.ImageElement img = new h.ImageElement(src:'packages/daxe/images/attributes.png', width:16, height:16);
        img.onClick.listen((h.MouseEvent event) => attributeButton(event));
        span.append(img);
      }
    }
    String title;
    if (_dn.ref != null) {
      title = doc.cfg.elementTitle(_dn.ref);
      if (title == null)
        title = _dn.nodeName;
    } else if (_dn is DNComment) {
      if (_type == START)
        title = "(";
      else
        title = ")";
    } else if (_dn is DNProcessingInstruction) {
      if (_type == START)
        title = "PI ${_dn.nodeName}";
      else
        title = "PI";
    } else if (_dn is DNCData)
      title = "CDATA";
    else if (_dn.nodeName != null)
      title = _dn.nodeName;
    else
      title = "?";
    if (_type != END) {
      bool listAllAttributes = (doc.cfg.elementParameterValue(_dn.ref,
          'attributsVisibles', 'false') == 'true');
      if (listAllAttributes) {
        span.append(new h.Text(title));
        for (DaxeAttr ja in _dn.attributes) {
          span.append(new h.Text(" "));
          h.Element nom_att = new h.SpanElement();
          nom_att.attributes['class'] = 'attribute_name';
          nom_att.text = ja.localName;
          span.append(nom_att);
          span.append(new h.Text("="));
          h.Element val_att = new h.SpanElement();
          val_att.attributes['class'] = 'attribute_value';
          val_att.text = ja.value;
          span.append(val_att);
        }
      } else {
        List<String> titleAttributes = doc.cfg.getElementParameters(_dn.ref)['titreAtt'];
        if (titleAttributes != null) {
          for (String attr in titleAttributes) {
            String value = _dn.getAttribute(attr);
            if (value != null && value != '') {
              title += " '$value'";
              break;
            }
          }
        }
        span.append(new h.Text(title));
      }
    } else
      span.append(new h.Text(title));
    span.onDoubleClick.listen((h.MouseEvent event) {
      page.selectNode(_dn);
      event.preventDefault();
      event.stopPropagation();
    });
    return(span);
  }
  
  void attributeButton(h.MouseEvent event) {
    _dn.attributeDialog();
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/toolbar.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/toolbar.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

class Toolbar {
  List<ToolbarItem> items;
  List<x.Element> cacheRefs = null;
  static final String iconPath = 'packages/daxe/images/toolbar/';
  
  Toolbar([Config cfg]) {
    items = new List<ToolbarItem>();
    if (doc.saveURL != null) {
      ToolbarBox documentBox = new ToolbarBox();
      documentBox.add(new ToolbarButton(Strings.get('menu.save'), iconPath + 'document_save.png',
          () => page.save(), null));
      items.add(documentBox);
    }
    ToolbarBox historyBox = new ToolbarBox();
    historyBox.add(new ToolbarButton(Strings.get('undo.undo'), iconPath + 'history_undo.png',
        () => doc.undo(), null, data:"undo", enabled:false));
    historyBox.add(new ToolbarButton(Strings.get('undo.redo'), iconPath + 'history_redo.png',
        () => doc.redo(), null, data:"redo", enabled:false));
    items.add(historyBox);
    ToolbarBox findBox = new ToolbarBox();
    findBox.add(new ToolbarButton(Strings.get('find.find_replace'), iconPath + 'find.png',
        () => (new FindDialog()).show(), null));
    items.add(findBox);
    if (cfg != null) {
      // Buttons to insert new elements
      ToolbarBox insertBox = new ToolbarBox();
      List<x.Element> refs = cfg.elementsWithType('fichier');
      if (refs != null && refs.length > 0) {
        addInsertButton(cfg, insertBox, refs, iconPath + 'insert_image.png');
      }
      refs = cfg.elementsWithType('equationmem');
      if (refs != null && refs.length > 0) {
        addInsertButton(cfg, insertBox, refs, iconPath + 'equation.png');
      }
      refs = cfg.elementsWithType('symbole2');
      if (refs != null && refs.length > 0) {
        addInsertButton(cfg, insertBox, refs, iconPath + 'insert_symbol.png');
      }
      refs = cfg.elementsWithType('tabletexte');
      if (refs != null && refs.length > 0) {
        addInsertButton(cfg, insertBox, refs, iconPath + 'insert_table.png');
      }
      refs = cfg.elementsWithType('liste');
      if (refs != null && refs.length > 0) {
        addInsertButton(cfg, insertBox, refs, iconPath + 'ul.png');
      }
      items.add(insertBox);
      // List buttons
      List<x.Element> ulRefs = DNWList.ulRefs();
      List<x.Element> olRefs = DNWList.olRefs();
      if (ulRefs.length > 0 || olRefs.length > 0) {
        ToolbarBox listBox = new ToolbarBox();
        if (ulRefs.length > 0) {
          ToolbarButton button = new ToolbarButton(_documentationFor(ulRefs[0]), iconPath + 'ul.png',
              null, listButtonUpdate, data:new ToolbarStyleInfo(ulRefs, null, null));
          button.action = () {
            if (button.selected)
              DNWList.riseLevel();
            else
              DNWList.addList((button.data as ToolbarStyleInfo).validRef);
          };
          listBox.add(button);
        }
        if (olRefs.length > 0) {
          ToolbarButton button = new ToolbarButton(_documentationFor(olRefs[0]), iconPath + 'ol.png',
              null, listButtonUpdate, data:new ToolbarStyleInfo(olRefs, null, null));
          button.action = () {
            if (button.selected)
              DNWList.riseLevel();
            else
              DNWList.addList((button.data as ToolbarStyleInfo).validRef);
          };
          listBox.add(button);
        }
        listBox.add(new ToolbarButton(Strings.get('toolbar.rise_list_level'), iconPath + 'list_rise_level.png',
            () => DNWList.riseLevel(), riseListLevelButtonUpdate, data:'rise_list_level'));
        bool possibleListInItem = true; // will be true if it is always possible to have a list in an item of the same type
        for (x.Element ulRef in ulRefs)
          if (doc.cfg.findSubElement(DNWList.findItemRef(ulRef), ulRefs) == null)
            possibleListInItem = false;
        for (x.Element olRef in olRefs)
          if (doc.cfg.findSubElement(DNWList.findItemRef(olRef), olRefs) == null)
            possibleListInItem = false;
        if (possibleListInItem)
          listBox.add(new ToolbarButton(Strings.get('toolbar.lower_list_level'), iconPath + 'list_lower_level.png',
              () => DNWList.lowerLevel(), lowerListLevelButtonUpdate, data:'lower_list_level'));
        items.add(listBox);
      }
      // Link/Anchor buttons
      List<x.Element> aRefs = DNAnchor.aRefs();
      if (aRefs != null && aRefs.length > 0) {
        ToolbarBox anchorBox = new ToolbarBox();
        ToolbarButton button = new ToolbarButton(Strings.get('toolbar.insert_link'),
            iconPath + 'add_link.png',
            null, insertLinkButtonUpdate,
            data:new ToolbarStyleInfo(aRefs, null, null));
        button.action = () => DNAnchor.addLink((button.data as ToolbarStyleInfo).validRef);
        anchorBox.add(button);
        button = new ToolbarButton(Strings.get('toolbar.remove_link'),
            iconPath + 'remove_link.png',
            () => DNAnchor.removeLink(), removeLinkButtonUpdate, 
            data:new ToolbarStyleInfo(aRefs, null, null));
        anchorBox.add(button);
        button = new ToolbarButton(Strings.get('toolbar.insert_anchor'),
            iconPath + 'anchor.png',
            null, insertButtonUpdate,
            data:new ToolbarStyleInfo(aRefs, null, null));
        button.action = () => DNAnchor.addAnchor((button.data as ToolbarStyleInfo).validRef);
        anchorBox.add(button);
        items.add(anchorBox);
      }
      // Style buttons
      ToolbarBox styleBox = new ToolbarBox();
      List<x.Element> all = cfg.allElementsList();
      for (x.Element ref in all) {
        String dtype = cfg.elementDisplayType(ref);
        if (dtype == 'style') {
          String style = cfg.elementParameterValue(ref, 'style', null);
          if (style == 'GRAS') {
            addStyleButton(cfg, styleBox, ref, iconPath + 'style_bold.png', 'B');
          } else if (style == 'ITALIQUE') {
            addStyleButton(cfg, styleBox, ref, iconPath + 'style_italic.png', 'I');
          } else if (style == 'EXPOSANT') {
            addStyleButton(cfg, styleBox, ref, iconPath + 'style_superscript.png');
          } else if (style == 'INDICE') {
            addStyleButton(cfg, styleBox, ref, iconPath + 'style_subscript.png');
          } else if (style == 'BARRE') {
            addStyleButton(cfg, styleBox, ref, iconPath + 'style_strikethrough.png');
          } else if (style == 'SOULIGNE') {
            addStyleButton(cfg, styleBox, ref, iconPath + 'style_underline.png');
          }
        }
      }
      if (styleBox.length > 0) {
        styleBox.add(new ToolbarButton(Strings.get('toolbar.remove_styles'), iconPath + 'remove_styles.png',
            () => DNStyle.removeStylesFromSelection(), null, data:"remove_styles"));
        items.add(styleBox);
      }
      if (doc.hiddenParaRefs != null) {
        // Align buttons
        String pStyleAtt = doc.cfg.elementParameterValue(doc.hiddenParaRefs[0], 'styleAtt', 'style');
        // check if style attribute is allowed for hidden paragraphs
        List<x.Element> attRefs = doc.cfg.elementAttributes(doc.hiddenParaRefs[0]);
        bool found = false;
        for (x.Element attRef in attRefs) {
          if (doc.cfg.attributeName(attRef) == pStyleAtt) {
            found = true;
            break;
          }
        }
        if (found) {
          ToolbarBox alignBox = new ToolbarBox();
          addParagraphCssButton(alignBox, 'text-align', 'left',
              Strings.get('toolbar.align_left'), iconPath + 'align_left.png');
          addParagraphCssButton(alignBox, 'text-align', 'right',
              Strings.get('toolbar.align_right'), iconPath + 'align_right.png');
          addParagraphCssButton(alignBox, 'text-align', 'center',
              Strings.get('toolbar.align_center'), iconPath + 'align_center.png');
          addParagraphCssButton(alignBox, 'text-align', 'justify',
              Strings.get('toolbar.align_justify'), iconPath + 'align_justify.png');
          items.add(alignBox);
        }
      }
      x.Element spanRef = DNStyleSpan.styleSpanRef();
      if (spanRef != null) {
        // Font menu
        List<String> fonts = ['serif', 'sans-serif', 'cursive', 'fantasy', 'monospace'];
        ToolbarMenu tbmenu = _makeStyleToolbarMenu(Strings.get('toolbar.font'), "font-family", fonts);
        items.add(tbmenu);
        /*
        A size menu is a bad idea: larger font sizes are used for titles and it is
        important to be able to extract titles automatically
        
        // Size menu
        List<String> sizes = ['8', '9', '10', '11', '12', '14', '16', '18',
                              '20', '24', '28', '32', '36', '48', '72'];
        tbmenu = _makeStyleToolbarMenu(Strings.get('toolbar.size'), "font-size", sizes, "px");
        items.add(tbmenu);
        */
      }
    }
  }
  
  ToolbarMenu _makeStyleToolbarMenu(String title, String cssName, List<String> cssValues,
                                    [String cssUnit]) {
    Menu menu = new Menu(title);
    menu.parent = this;
    x.Element styleRef = DNStyleSpan.styleSpanRef();
    for (String cssValue in cssValues) {
      String cssValueWithUnit = cssValue;
      if (cssUnit != null)
        cssValueWithUnit += cssUnit;
      MenuItem menuItem = new MenuItem(cssValue, null,
          data:new ToolbarStyleInfo([styleRef], cssName, cssValueWithUnit));
      menuItem.action = () {
        if (menuItem.checked) {
          Position start = page.getSelectionStart();
          Position end = page.getSelectionEnd();
          if (start == end && start.dn is DNText && start.dn.parent.ref == styleRef &&
              (start.dn.parent as DNStyle).matchesCss(cssName, cssValueWithUnit) &&
              start.dnOffset == start.dn.offsetLength && start.dn.nextSibling == null) {
            // we are at the end of the style
            // just move the cursor position outside of the style
            DaxeNode styleNode = start.dn.parent;
            page.moveCursorTo(new Position(styleNode.parent, styleNode.parent.offsetOf(styleNode)+1));
            page.updateAfterPathChange();
          } else
            DNStyle.removeStylesFromSelection(styleRef, cssName);
        } else {
          DNStyle.removeAndApplyStyleToSelection(styleRef, cssName, cssValueWithUnit);
        }
      };
      menu.add(menuItem);
    }
    ToolbarMenu tbmenu = new ToolbarMenu(menu, styleMenuUpdate, this);
    return(tbmenu);
  }
  
  add(ToolbarItem item) {
    items.add(item);
  }
  
  insert(ToolbarItem item, int position) {
    items.insert(position, item);
  }
  
  remove(int position) {
    items.remove(position);
  }
  
  List<ToolbarButton> get buttons {
    List<ToolbarButton> buttons = new List<ToolbarButton>();
    for (ToolbarItem item in items) {
      if (item is ToolbarBox)
        buttons.addAll(item.buttons);
    }
    return(buttons);
  }
  
  /**
   * Returns a list of all element references used in the toolbar.
   */
  List<x.Element> elementRefs() {
    if (cacheRefs != null)
      return(cacheRefs);
    List<x.Element> list = new List<x.Element>();
    for (ToolbarItem item in items) {
      if (item is ToolbarBox) {
        for (ToolbarButton button in item.buttons) {
          if (button.data is ToolbarStyleInfo) {
            ToolbarStyleInfo info = button.data;
            if (info.possibleRefs != null)
              list.addAll(info.possibleRefs);
          }
        }
      } else if (item is ToolbarMenu) {
        Menu menu = item.menu;
        for (MenuItem menuItem in menu.items) {
          if (menuItem.data is ToolbarStyleInfo) {
            ToolbarStyleInfo info = menuItem.data;
            if (info.possibleRefs != null)
              list.addAll(info.possibleRefs);
          }
        }
      }
    }
    cacheRefs = list;
    return(list);
  }
  
  h.Element html() {
    h.DivElement div = new h.DivElement();
    div.classes.add('toolbar');
    for (ToolbarItem item in items) {
      div.append(item.html());
    }
    return(div);
  }
  
  String _documentationFor(x.Element ref) {
    String documentation = doc.cfg.documentation(ref);
    if (documentation == null)
      documentation = doc.cfg.elementTitle(ref);
    return(documentation);
  }
  
  addInsertButton(Config cfg, ToolbarBox box, List<x.Element> refs, String icon) {
    ToolbarButton button = new ToolbarButton(_documentationFor(refs[0]), icon,
        null, insertButtonUpdate,
        data:new ToolbarStyleInfo(refs, null, null));
    button.action = () => doc.insertNewNode((button.data as ToolbarStyleInfo).validRef, 'element');
    box.add(button);
  }
  
  static void insertButtonUpdate(ToolbarButton button, DaxeNode parent, DaxeNode selectedNode,
                             List<x.Element> validRefs, List<x.Element> ancestorRefs) {
    // element insert
    ToolbarStyleInfo info = button.data;
    List<x.Element> refs = info.possibleRefs;
    if (info.findValidRef(validRefs))
      button.enable();
    else
      button.disable();
  }
  
  addStyleButton(Config cfg, ToolbarBox box, x.Element ref, String icon, [String shortcut=null]) {
    ToolbarButton button = new ToolbarButton(_documentationFor(ref), icon, null, dnStyleButtonUpdate,
        data:new ToolbarStyleInfo([ref], null, null), shortcut:shortcut);
    button.action = () {
      if (button.selected) {
        Position start = page.getSelectionStart();
        Position end = page.getSelectionEnd();
        if (start == end && start.dn is DNText && start.dn.parent.ref == ref &&
            start.dnOffset == start.dn.offsetLength && start.dn.nextSibling == null) {
          // we are at the end of the style
          // just move the cursor position outside of the style
          DaxeNode styleNode = start.dn.parent;
          page.moveCursorTo(new Position(styleNode.parent, styleNode.parent.offsetOf(styleNode)+1));
          page.updateAfterPathChange();
        } else
          DNStyle.removeStylesFromSelection(ref);
      } else {
        DNStyle.applyStyleInsideSelection(ref);
      }
    };
    box.add(button);
  }
  
  static void dnStyleButtonUpdate(ToolbarButton button, DaxeNode parent, DaxeNode selectedNode,
                             List<x.Element> validRefs, List<x.Element> ancestorRefs) {
    // DNStyle, style with no css (as with the b element)
    ToolbarStyleInfo info = button.data;
    List<x.Element> refs = info.possibleRefs;
    bool foundAncestor = false;
    for (x.Element possibleRef in refs) {
      if (ancestorRefs.contains(possibleRef)) {
        foundAncestor = true;
        break;
      }
    }
    if (foundAncestor) {
      button.enable();
      button.select();
    } else {
      if (selectedNode != null && refs.contains(selectedNode.ref))
        button.select();
      else
        button.deselect();
      if (info.findValidRef(validRefs))
        button.enable();
      else
        button.disable();
    }
  }
  
  static void styleSpanButtonUpdate(ToolbarButton button, DaxeNode parent, DaxeNode selectedNode,
                             List<x.Element> validRefs, List<x.Element> ancestorRefs) {
    // DNSpanStyle, span style
    ToolbarStyleInfo info = button.data;
    x.Element ref = info.possibleRefs[0];
    bool foundAncestor = false;
    for (DaxeNode n = parent; n != null; n = n.parent) {
      if (n.ref == ref && (n as DNStyleSpan).matchesCss(info.cssName, info.cssValue)) {
        foundAncestor = true;
        break;
      }
    }
    if (foundAncestor) {
      button.enable();
      button.select();
    } else {
      if (selectedNode != null && ref == selectedNode.ref &&
          (selectedNode as DNStyleSpan).matchesCss(info.cssName, info.cssValue)) {
        button.select();
      } else {
        button.deselect();
      }
      if (validRefs.contains(ref))
        button.enable();
      else
        button.disable();
    }
  }
  
  addParagraphCssButton(ToolbarBox alignBox, String cssName, String cssValue,
                         String title, String icon) {
    ToolbarButton button = new ToolbarButton(title, icon,
        null, paragraphButtonUpdate, data:new ToolbarStyleInfo(doc.hiddenParaRefs, cssName, cssValue));
    button.action = () {
      if (button.selected) {
        DNHiddenP.removeStyleFromSelection(cssName);
      } else {
        DNHiddenP.applyStyleToSelection(cssName, cssValue);
      }
    };
    alignBox.add(button);
  }
  
  static void paragraphButtonUpdate(ToolbarButton button, DaxeNode parent, DaxeNode selectedNode,
                             List<x.Element> validRefs, List<x.Element> ancestorRefs) {
    ToolbarStyleInfo info = button.data;
    bool foundAncestor = false;
    for (DaxeNode n = parent; n != null; n = n.parent) {
      if (doc.hiddenParaRefs.contains(n.ref)) {
        if ((n as DNHiddenP).matchesCss(info.cssName, info.cssValue)) {
          foundAncestor = true;
          break;
        }
      }
    }
    if (foundAncestor) {
      button.enable();
      button.select();
    } else {
      if (selectedNode != null && doc.hiddenParaRefs.contains(selectedNode.ref) &&
          (selectedNode as DNHiddenP).matchesCss(info.cssName, info.cssValue)) {
        button.select();
      } else {
        button.deselect();
      }
      if (DNHiddenP.paragraphsInSelection().length > 0)
        button.enable();
      else
        button.disable();
    }
  }
  
  static void listButtonUpdate(ToolbarButton button, DaxeNode parent, DaxeNode selectedNode,
                             List<x.Element> validRefs, List<x.Element> ancestorRefs) {
    // toggle list button
    ToolbarStyleInfo info = button.data;
    List<x.Element> refs = info.possibleRefs;
    bool foundAncestor = false;
    for (DaxeNode n = parent; n != null; n = n.parent) {
      if (refs.contains(n.ref)) {
        foundAncestor = true;
        break;
      }
    }
    if (foundAncestor) {
      button.enable();
      button.select();
    } else {
      button.deselect();
      if (info.findValidRef(validRefs))
        button.enable();
      else
        button.disable();
    }
  }
  
  static void riseListLevelButtonUpdate(ToolbarButton button, DaxeNode parent, DaxeNode selectedNode,
                             List<x.Element> validRefs, List<x.Element> ancestorRefs) {
    Position start = page.getSelectionStart();
    DaxeNode dn = start.dn;
    while (dn != null && dn is! DNWItem)
      dn = dn.parent;
    if (dn != null)
      button.enable();
    else
      button.disable();
  }
  
  static void lowerListLevelButtonUpdate(ToolbarButton button, DaxeNode parent, DaxeNode selectedNode,
                             List<x.Element> validRefs, List<x.Element> ancestorRefs) {
    Position start = page.getSelectionStart();
    DaxeNode dn = start.dn;
    while (dn != null && dn is! DNWItem)
      dn = dn.parent;
    if (dn == null || dn.previousSibling == null)
      button.disable();
    else
      button.enable();
  }
  
  static void insertLinkButtonUpdate(ToolbarButton button, DaxeNode parent, DaxeNode selectedNode,
                             List<x.Element> validRefs, List<x.Element> ancestorRefs) {
    ToolbarStyleInfo info = button.data;
    if (page.getSelectionStart().dn is DNText && info.findValidRef(validRefs))
      button.enable();
    else
      button.disable();
  }
  
  static void removeLinkButtonUpdate(ToolbarButton button, DaxeNode parent, DaxeNode selectedNode,
                             List<x.Element> validRefs, List<x.Element> ancestorRefs) {
    ToolbarStyleInfo info = button.data;
    List<x.Element> refs = info.possibleRefs;
    bool foundAncestor = false;
    for (x.Element possibleRef in refs) {
      if (ancestorRefs.contains(possibleRef)) {
        foundAncestor = true;
        break;
      }
    }
    if (foundAncestor)
      button.enable();
    else
      button.disable();
  }
  
  static void styleMenuUpdate(ToolbarMenu tbmenu, DaxeNode parent, DaxeNode selectedNode,
                             List<x.Element> validRefs, List<x.Element> ancestorRefs) {
    Menu menu = tbmenu.menu;
    MenuItem selectedItem = null;
    for (MenuItem menuItem in menu.items) {
      if (menuItem.data is ToolbarStyleInfo) {
        ToolbarStyleInfo info = menuItem.data;
        x.Element ref = info.possibleRefs[0];
        String cssName = info.cssName;
        String cssValue = info.cssValue;
        if (doc.hiddenParaRefs.contains(ref)) {
          // paragraph style
          bool foundAncestor = false;
          for (DaxeNode n = parent; n != null; n = n.parent) {
            if (doc.hiddenParaRefs.contains(n.ref) && (n as DNHiddenP).matchesCss(cssName, cssValue)) {
              foundAncestor = true;
              break;
            }
          }
          if (foundAncestor) {
            menuItem.enable();
            selectedItem = menuItem;
            menuItem.check();
          } else {
            if (selectedNode != null && doc.hiddenParaRefs.contains(selectedNode.ref) &&
                (selectedNode as DNHiddenP).matchesCss(cssName, cssValue)) {
              selectedItem = menuItem;
              menuItem.check();
            } else {
              menuItem.uncheck();
            }
            if (DNHiddenP.paragraphsInSelection().length > 0)
              menuItem.enable();
            else
              menuItem.disable();
          }
        } else if (doc.cfg.elementDisplayType(ref) == 'style') {
          // DNStyle
          if (ancestorRefs.contains(ref)) {
            menuItem.enable();
            selectedItem = menuItem;
          } else {
            if (selectedNode != null && ref == selectedNode.ref)
              selectedItem = menuItem;
            if (validRefs.contains(ref))
              menuItem.enable();
            else
              menuItem.disable();
          }
        } else if (doc.cfg.elementDisplayType(ref) == 'stylespan') {
          // DNSpanStyle
          bool foundAncestor = false;
          for (DaxeNode n = parent; n != null; n = n.parent) {
            if (n.ref == ref && (n as DNStyleSpan).matchesCss(cssName, cssValue)) {
              foundAncestor = true;
              break;
            }
          }
          if (foundAncestor) {
            menuItem.enable();
            selectedItem = menuItem;
            menuItem.check();
          } else {
            if (selectedNode != null && ref == selectedNode.ref &&
                (selectedNode as DNStyleSpan).matchesCss(cssName, cssValue)) {
              selectedItem = menuItem;
              menuItem.check();
            } else {
              menuItem.uncheck();
            }
            if (validRefs.contains(ref))
              menuItem.enable();
            else
              menuItem.disable();
          }
        } else if (ref != null) {
          // element insert
          if (validRefs.contains(ref))
            menuItem.enable();
          else
            menuItem.disable();
        }
      }
    }
    if (selectedItem == null)
      menu.title = tbmenu.title;
    else
      menu.title = selectedItem.title;
  }
  
  static void insertMenuUpdate(ToolbarMenu tbmenu, DaxeNode parent, DaxeNode selectedNode,
                             List<x.Element> validRefs, List<x.Element> ancestorRefs) {
    Menu menu = tbmenu.menu;
    for (MenuItem menuItem in menu.items) {
      if (menuItem.data is ToolbarStyleInfo) {
        ToolbarStyleInfo info = menuItem.data;
        List<x.Element> refs = info.possibleRefs;
        if (refs != null && refs.length > 0) {
          // element insert
          if (info.findValidRef(validRefs))
            menuItem.enable();
          else
            menuItem.disable();
        }
      }
    }
  }
  
  void update(DaxeNode parent, List<x.Element> validRefs) {
    List<x.Element> ancestorRefs = new List<x.Element>();
    for (DaxeNode n = parent; n != null; n = n.parent)
      ancestorRefs.add(n.ref);
    DaxeNode selectedNode = null;// will be !=null when the selection matches a full element
    Position start = page.getSelectionStart();
    Position end = page.getSelectionEnd();
    if (start.dn is! DNText && start.dn.offsetLength > start.dnOffset) {
      if (end == new Position(start.dn, start.dnOffset + 1))
        selectedNode = start.dn.childAtOffset(start.dnOffset);
    }
    // update buttons
    for (ToolbarButton button in buttons) {
      if (button.update != null)
        button.update(button, parent, selectedNode, validRefs, ancestorRefs);
    }
    // update menus
    for (ToolbarItem tbitem in items) {
      if (tbitem is ToolbarMenu) {
        tbitem.update(tbitem, parent, selectedNode, validRefs, ancestorRefs);
      }
    }
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/toolbar_box.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/toolbar_box.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

class ToolbarBox extends ToolbarItem {
  List<ToolbarButton> buttons;
  
  ToolbarBox() {
    buttons = new List<ToolbarButton>();
  }
  
  add(ToolbarButton button) {
    buttons.add(button);
  }
  
  insert(ToolbarButton button, int position) {
    buttons.insert(position, button);
  }
  
  remove(int position) {
    buttons.remove(position);
  }
  
  get length {
    return(buttons.length);
  }
  
  h.Element html() {
    h.DivElement div = new h.DivElement();
    div.classes.add('toolbar-box');
    for (ToolbarButton button in buttons) {
      div.append(button.html());
    }
    return(div);
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/toolbar_button.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/toolbar_button.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

typedef void UpdateButtonState(ToolbarButton button, DaxeNode parent, DaxeNode selectedNode,
                               List<x.Element> validRefs, List<x.Element> ancestorRefs);

class ToolbarButton {
  static int idcount = 0;
  String _title;
  String iconFilename;
  ActionFunction action;
  UpdateButtonState update;
  Object data;
  String id;
  bool enabled;
  bool selected;
  StreamSubscription<h.MouseEvent> listener;
  String shortcut;
  int iconWidth;
  int iconHeight;
  
  ToolbarButton(this._title, this.iconFilename, this.action, this.update,
      {this.data, this.enabled:true, this.shortcut, this.iconWidth:16, this.iconHeight:16}) {
    selected = false;
    id = "button_$idcount";
    idcount++;
  }
  
  h.Element html() {
    h.DivElement div = new h.DivElement();
    div.id = id;
    div.classes.add('toolbar-button');
    if (!enabled)
      div.classes.add('button-disabled');
    else
      div.setAttribute('tabindex', '0');
    if (selected)
      div.classes.add('button-selected');
    div.setAttribute('title', _title);
    h.ImageElement img = new h.ImageElement();
    img.src = iconFilename;
    img.width = iconWidth;
    img.height = iconHeight;
    if (enabled)
      listener = div.onClick.listen((h.MouseEvent event) => action());
    div.append(img);
    div.onKeyDown.listen((h.KeyboardEvent event) {
      int keyCode = event.keyCode;
      if (keyCode == h.KeyCode.ENTER) {
        event.preventDefault();
        action();
      }
    });
    return(div);
  }
  
  String get title {
    return(_title);
  }
  
  void set title(String t) {
    _title = t;
    h.Element div = getHTMLNode();
    div.setAttribute('title', _title);
  }
  
  h.Element getHTMLNode() {
    return(h.querySelector("#$id"));
  }
  
  void disable() {
    if (!enabled)
      return;
    enabled = false;
    h.Element div = getHTMLNode();
    div.classes.add('button-disabled');
    listener.cancel();
    div.setAttribute('tabindex', '-1');
  }
  
  void enable() {
    if (enabled)
      return;
    enabled = true;
    h.Element div = getHTMLNode();
    div.classes.remove('button-disabled');
    listener = div.onClick.listen((h.MouseEvent event) => action());
    div.setAttribute('tabindex', '0');
  }
  
  void select() {
    if (selected)
      return;
    selected = true;
    h.Element div = getHTMLNode();
    div.classes.add('button-selected');
  }
  
  void deselect() {
    if (!selected)
      return;
    selected = false;
    h.Element div = getHTMLNode();
    div.classes.remove('button-selected');
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/toolbar_item.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/toolbar_item.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

abstract class ToolbarItem {
  h.Element html();
}

Index: modules/damieng/graphical_editor/daxe/lib/src/toolbar_menu.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/toolbar_menu.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

typedef void UpdateMenuState(ToolbarMenu tbmenu, DaxeNode parent, DaxeNode selectedNode,
                               List<x.Element> validRefs, List<x.Element> ancestorRefs);

class ToolbarMenu extends ToolbarItem {
  Menu menu;
  String title;
  UpdateMenuState update;
  
  ToolbarMenu(this.menu, this.update, Toolbar toolbar) {
    title = menu.title;
    menu.parent = toolbar;
  }
  
  h.Element html() {
    h.DivElement div = new h.DivElement();
    div.classes.add('toolbar-menu');
    div.append(page.mbar.createMenuDiv(menu));
    return(div);
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/toolbar_style_info.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/toolbar_style_info.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

class ToolbarStyleInfo {
  List<x.Element> possibleRefs; /* list of possible element references for the toolbar item */
  x.Element validRef; /* one of possibleRefs, which can be used now */
  String cssName;
  String cssValue;
  
  ToolbarStyleInfo(this.possibleRefs, this.cssName, this.cssValue) {
    assert(possibleRefs != null && possibleRefs.length > 0 &&
        (cssName == null || cssValue != null));
    validRef = null;
  }
  
  String get css {
    if (cssName == null)
      return(null);
    return("$cssName: $cssValue");
  }
  
  bool findValidRef(List<x.Element> list) {
    for (x.Element possibleRef in possibleRefs) {
      if (list.contains(possibleRef)) {
        validRef = possibleRef;
        return(true);
      }
    }
    validRef = null;
    return(false);
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/tree_item.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/tree_item.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

/**
 * Visible element in the tree panel.
 */
class TreeItem {
  DaxeNode dn;
  TreeItem parent;
  TreeItem firstChild;
  TreeItem nextSibling;
  bool expanded;
  h.DivElement div;
  h.SpanElement titleSpan;
  h.SpanElement expandButton;
  
  TreeItem(this.dn, this.parent) {
    expanded = false;
    createDiv();
    if (dn.parent == doc.dndoc) {
      // expand root automatically
      toggleExpand();
    }
  }
  
  void createChildren() {
    for (DaxeNode child = dn.firstChild; child != null; child=child.nextSibling) {
      if (child is! DNText) {
        appendChild(new TreeItem(child, this));
      }
    }
  }
  
  void removeChildren() {
    if (firstChild != null)
      removeChildrenStartingAt(firstChild);
  }
  
  void removeChildrenStartingAt(TreeItem child) {
    TreeItem item = child;
    while (item != null) {
      if (item.div != null)
        item.div.remove();
      item = item.nextSibling;
    }
    if (firstChild == child)
      firstChild = null;
    else
      child.previousSibling.nextSibling = null;
  }
  
  TreeItem get lastChild {
    if (firstChild == null)
      return null;
    TreeItem item = firstChild;
    while (item.nextSibling != null) {
      item = item.nextSibling;
    }
    return item;
  }
  
  TreeItem get previousSibling {
    if (parent == null)
      return null;
    for (TreeItem item=parent.firstChild; item != null; item=item.nextSibling) {
      if (item.nextSibling == this)
        return item;
    }
    return null;
  }
  
  List<TreeItem> get children {
    List<TreeItem> list = new List<TreeItem>();
    for (TreeItem item=firstChild; item != null; item=item.nextSibling)
      list.add(item);
    return(list);
  }
  
  void appendChild(TreeItem child) {
    TreeItem last = lastChild;
    if (last == null) {
      firstChild = child;
    } else {
      last.nextSibling = child;
    }
    child.nextSibling = null;
    child.parent = this;
    div.append(child.div);
  }
  
  bool get canExpand {
    return hasNonTextChild(dn);
  }
  
  createDiv() {
    div = new h.DivElement();
    div.classes.add('tree_div');
    titleSpan = new h.SpanElement();
    titleSpan.classes.add('tree_node_title');
    titleSpan.setAttribute('tabindex', '-1');
    titleSpan.onKeyDown.listen((h.KeyboardEvent event) {
      int keyCode = event.keyCode;
      if (keyCode == h.KeyCode.DOWN) {
        if (firstChild != null)
          firstChild.focus();
        else if (nextSibling != null)
          nextSibling.focus();
        else if (parent != null) {
          TreeItem item = parent;
          while (item.nextSibling == null && item.parent != null)
            item = item.parent;
          if (item.nextSibling != null)
            item.nextSibling.focus();
        }
      } else if (keyCode == h.KeyCode.UP) {
        if (previousSibling != null) {
          TreeItem item = previousSibling;
          while (item.lastChild != null)
            item = item.lastChild;
          item.focus();
        } else if (parent != null)
          parent.focus();
        else
          h.document.getElementById('tree_tab_button').focus();
      } else if (keyCode == h.KeyCode.ENTER) {
        page.scrollToNode(dn);
      } else if (keyCode == h.KeyCode.RIGHT && canExpand) {
        if (!expanded)
          toggleExpand();
        firstChild.focus();
      } else if (keyCode == h.KeyCode.LEFT && expanded) {
        toggleExpand();
      } else if (keyCode == h.KeyCode.LEFT && parent != null) {
        parent.focus();
      }
    });
    if (dn.ref != null)
      titleSpan.appendText(doc.cfg.elementTitle(dn.ref));
    else
      titleSpan.appendText(dn.nodeName);
    titleSpan.onClick.listen((h.MouseEvent event) => page.scrollToNode(dn));
    if (hasNonTextChild(dn) && dn.parent != doc.dndoc) {
      // (root is always expanded, no need for a button)
      addExpandButton();
    }
    div.append(titleSpan);
  }
  
  void addExpandButton() {
    expandButton = new h.SpanElement();
    expandButton.classes.add('expand_button');
    h.ImageElement img = new h.ImageElement(width:9, height:9);
    if (expanded) {
      expandButton.classes.add('expanded');
      img.src = 'packages/daxe/images/expanded_tree.png';
    } else {
      expandButton.classes.add('collapsed');
      img.src = 'packages/daxe/images/collapsed_tree.png';
    }
    expandButton.append(img);
    expandButton.onClick.listen((h.MouseEvent event) => toggleExpand());
    div.append(expandButton);
  }
  
  void toggleExpand() {
    if (!expanded) {
      // expand
      createChildren();
      if (expandButton != null) {
        expandButton.classes.remove('collapsed');
        expandButton.classes.add('expanded');
        h.ImageElement img = expandButton.firstChild;
        img.src = 'packages/daxe/images/expanded_tree.png';
      }
    } else {
      // collapse
      removeChildren();
      if (expandButton != null) {
        expandButton.classes.remove('expanded');
        expandButton.classes.add('collapsed');
        h.ImageElement img = expandButton.firstChild;
        img.src = 'packages/daxe/images/collapsed_tree.png';
      }
    }
    expanded = !expanded;
  }
  
  void update() {
    if (expandButton == null && hasNonTextChild(dn) && dn.parent != doc.dndoc) {
      addExpandButton();
      expanded = true;
    } else if (expandButton != null && !hasNonTextChild(dn)) {
      removeChildren();
      expanded = false;
      expandButton.remove();
    }
    if (!expanded)
      return;
    TreeItem item = firstChild;
    for (DaxeNode child = dn.firstChild; child != null; child=child.nextSibling) {
      if (child is! DNText) {
        if (item == null) {
          item = new TreeItem(child, this);
          appendChild(item);
        } else if (item.dn != child) {
          removeChildrenStartingAt(item);
          item = new TreeItem(child, this);
          appendChild(item);
        } else {
          item.update();
        }
      }
      if (item != null)
        item = item.nextSibling;
    }
    if (item != null)
      removeChildrenStartingAt(item);
  }
  
  void focus() {
    titleSpan.focus();
  }
  
  static bool hasNonTextChild(DaxeNode dn) {
    for (DaxeNode child = dn.firstChild; child != null; child=child.nextSibling) {
      if (child is! DNText) {
        return(true);
      }
    }
    return(false);
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/tree_panel.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/tree_panel.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

/**
 * Left panel showing the element tree.
 */
class TreePanel {
  TreeItem rootItem;
  
  TreePanel() {
  }
  
  void update() {
    if (rootItem == null && doc.getRootElement() != null) {
      rootItem = new TreeItem(doc.getRootElement(), null);
      _initialExpand();
      h.DivElement treeDiv = h.document.getElementById('tree');
      treeDiv.append(rootItem.div);
    } else {
      if (rootItem == null) {
        if (doc.getRootElement() != null) {
          rootItem = new TreeItem(doc.getRootElement(), null);
          h.DivElement treeDiv = h.document.getElementById('tree');
          treeDiv.append(rootItem.div);
        }
      } else  {
        if (doc.getRootElement() == null) {
          rootItem.div.remove();
          rootItem = null;
        } else
          rootItem.update();
      }
    }
  }
  
  /**
   * After opening a document, expand only root and the first level under root.
   */
  void _initialExpand() {
    if (!rootItem.canExpand)
      return;
    if (!rootItem.expanded)
      rootItem.toggleExpand();
    if (rootItem.children.length < 10) {
      for (TreeItem item=rootItem.firstChild; item != null; item=item.nextSibling) {
        if (item.canExpand && !item.expanded)
          item.toggleExpand();
      }
    }
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/undoable_edit.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/undoable_edit.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

/**
 * An undoable edit in the XML document.
 */
class UndoableEdit {
  static const int INSERT = 0;
  static const int REMOVE = 1;
  static const int ATTRIBUTES = 2;
  static const int COMPOUND = 3;
  
  int operation;
  String title;
  Position pos;
  String text;
  int length;
  DaxeNode dn;
  DaxeNode cutNode;
  List<DaxeAttr> attributes;
  List<UndoableEdit> subEdits;
  bool updateDisplay; // update the display the first time doit() is called (default: true)
  
  
  // CONSTRUCTORS
  
  UndoableEdit.insertString(Position pos, String text, {bool updateDisplay: true}) {
    assert(text != null && text != '');
    operation = INSERT;
    title = Strings.get('undo.insert_text');
    this.pos = new Position.clone(pos);
    this.text = text;
    dn = null; // we can't check if a new text node is needed at this point, because of CompoundEdits
    cutNode = null;
    attributes = null;
    subEdits = null;
    this.updateDisplay = updateDisplay;
  }
  
  UndoableEdit.removeString(Position pos, int length, {bool updateDisplay: true}) {
    assert(length > 0);
    assert(pos.dn.nodeType == DaxeNode.TEXT_NODE);
    operation = REMOVE;
    title = Strings.get('undo.remove_text');
    this.pos = new Position.clone(pos);
    text = null; // we can't extract it at this point, because of CompoundEdits
    this.length = length;
    dn = null;
    cutNode = null;
    attributes = null;
    subEdits = null;
    this.updateDisplay = updateDisplay;
  }
  
  UndoableEdit.insertNode(Position pos, DaxeNode dn, {bool updateDisplay: true}) {
    operation = INSERT;
    title = Strings.get('undo.insert_element');
    this.pos = new Position.clone(pos);
    text = null;
    this.dn = dn;
    cutNode = null;
    attributes = null;
    subEdits = null;
    this.updateDisplay = updateDisplay;
  }
  
  UndoableEdit.removeNode(DaxeNode dn, {bool updateDisplay: true}) {
    operation = REMOVE;
    title = Strings.get('undo.remove_element');
    pos = null;
    text = null;
    this.dn = dn;
    cutNode = null;
    attributes = null;
    subEdits = null;
    this.updateDisplay = updateDisplay;
  }
  
  UndoableEdit.changeAttributes(DaxeNode dn, List<DaxeAttr> attributes, {bool updateDisplay: true}) {
    operation = ATTRIBUTES;
    title = Strings.get('undo.attributes');
    pos = null;
    text = null;
    this.dn = dn;
    cutNode = null;
    this.attributes = attributes;
    subEdits = null;
    this.updateDisplay = updateDisplay;
  }
  
  UndoableEdit.changeAttribute(DaxeNode dn, DaxeAttr attr, {bool updateDisplay: true}) {
    operation = ATTRIBUTES;
    title = Strings.get('undo.attributes');
    pos = null;
    text = null;
    this.dn = dn;
    cutNode = null;
    LinkedHashMap<String, DaxeAttr> map = dn.getAttributesMapCopy(); // FIXME: not good with compounds
    if (attr.value == null) {
      if (map[attr.name] != null)
        map.remove(attr.name);
    } else
      map[attr.name] = attr;
    this.attributes = new List.from(map.values);
    subEdits = null;
    this.updateDisplay = updateDisplay;
  }
  
  UndoableEdit.compound(String title) {
    operation = COMPOUND;
    this.title = title;
    pos = null;
    text = null;
    dn = null;
    cutNode = null;
    attributes = null;
    subEdits = new List<UndoableEdit>();
    updateDisplay = true;
  }
  
  
  // METHODS
  
  /**
   * Returns true if the edit has been successfully added to this one
   */
  bool addEdit(UndoableEdit edit) {
    if (operation != edit.operation)
      return(false);
    if (operation == INSERT && dn is DNText && edit.text != null &&
        edit.pos.dn == dn && edit.pos.dnOffset + 1 == dn.offsetLength) {
      // insert text node + insert text
      return(true);
    }
    // TODO: handle more text node merges
    // the rest is for text nodes only
    if (text == null || edit.text == null)
      return(false);
    if (dn != edit.dn)
      return(false);
    if ((operation == INSERT && edit.pos.dnOffset == pos.dnOffset + text.length) ||
        (operation == REMOVE && edit.pos.dnOffset == pos.dnOffset)) {
      text = "$text${edit.text}";
      return(true);
    }
    if ((operation == INSERT && edit.pos.dnOffset == pos.dnOffset) ||
        (operation == REMOVE && pos.dnOffset == edit.pos.dnOffset + edit.text.length)) {
      text = "${edit.text}$text";
      if (operation == REMOVE)
        pos = new Position(pos.dn, pos.dnOffset - edit.text.length);
      return(true);
    }
    return(false);
  }
  
  void addSubEdit(UndoableEdit edit) {
    assert(operation == COMPOUND);
    subEdits.add(edit);
  }
  
  void doit() {
    if (operation == INSERT)
      _insert(updateDisplay);
    else if (operation == REMOVE)
      _remove(updateDisplay);
    else if (operation == ATTRIBUTES)
      _changeAttributes(updateDisplay);
    else if (operation == COMPOUND) {
      for (UndoableEdit edit in subEdits) {
        edit.doit();
      }
    }
    updateDisplay = true;
  }
  
  void undo() {
    if (operation == INSERT)
      _remove(updateDisplay);
    else if (operation == REMOVE)
      _insert(updateDisplay);
    else if (operation == ATTRIBUTES)
      _changeAttributes(updateDisplay);
    else if (operation == COMPOUND) {
      for (UndoableEdit edit in subEdits.reversed) {
        edit.undo();
      }
    }
  }
  
  void _insert(bool update) {
    if (text != null) {
      // for UndoableEdit.insertString :
      pos.moveInsideTextNodeIfPossible();
      if (pos.dn.nodeType == DaxeNode.ELEMENT_NODE) {
        // a new text node is required (it turns this edit into an insertNode)
        if (pos.dn.needsParentUpdatingDNText)
          dn = new ParentUpdatingDNText(text);
        else
          dn = new DNText(text);
        text = null;
      }
    }
    if (text != null) {
      assert(pos.dn.nodeType == DaxeNode.TEXT_NODE);
      (pos.dn as DNText).insertString(pos, text);
      if (update) {
        pos.dn.updateHTML();
        page.moveCursorTo(new Position(pos.dn, pos.dnOffset + text.length));
        if (pos.dn.previousSibling == null && pos.dn.nextSibling == null) {
          // the parent might be a simple type
          // NOTE: this might slow down text input
          pos.dn.parent.updateValidity();
        }
      }
    } else {
      DaxeNode parent = pos.dn;
      DaxeNode parentToUpdate;
      List<DaxeNode> childrenUpdate = new List<DaxeNode>();
      childrenUpdate.add(dn);
      if (parent.nodeType == DaxeNode.TEXT_NODE && dn.nodeType == DaxeNode.TEXT_NODE) {
        String s1 = parent.nodeValue.substring(0, pos.dnOffset);
        String s2 = parent.nodeValue.substring(pos.dnOffset);
        parent.nodeValue = "$s1${dn.nodeValue}$s2";
        parentToUpdate = parent;
      } else if (pos.dnOffset == 0) {
        if (parent.nodeType == DaxeNode.TEXT_NODE) {
          parent.parent.insertBefore(dn, parent);
          parentToUpdate = parent.parent;
        } else {
          parent.insertBefore(dn, parent.firstChild);
          parentToUpdate = parent;
        }
      } else if (parent.offsetLength == pos.dnOffset) {
        if (parent.nodeType == DaxeNode.TEXT_NODE) {
          parent.parent.insertBefore(dn, parent.nextSibling);
          parentToUpdate = parent.parent;
        } else {
          parent.appendChild(dn);
          parentToUpdate = parent;
        }
      } else if (parent.nodeType == DaxeNode.TEXT_NODE) {
        if (cutNode == null)
          cutNode = (parent as DNText).cut(pos.dnOffset);
        else {
          parent.nodeValue = parent.nodeValue.substring(0, pos.dnOffset);
          parent.parent.insertAfter(cutNode, parent);
        }
        parent.parent.insertBefore(dn, cutNode);
        childrenUpdate.add(parent);
        childrenUpdate.add(cutNode);
        parentToUpdate = parent.parent;
      } else {
        DaxeNode next = parent.childAtOffset(pos.dnOffset);
        parent.insertBefore(dn, next);
        parentToUpdate = parent;
      }
      if (update) {
        if (dn.nodeType == DaxeNode.ELEMENT_NODE)
          dn.updateValidity();
        parentToUpdate.updateValidity();
        parentToUpdate.updateHTMLAfterChildrenChange(childrenUpdate);
        if (dn is DNText)
          page.moveCursorTo(new Position(dn, dn.nodeValue.length));
        else
          page.moveCursorTo(new Position(dn.parent, dn.parent.offsetOf(dn) + 1));
      }
      dn.callAfterInsert();
    }
  }
  
  void _remove(bool update) {
    if (dn == null && text == null) {
      // for UndoableEdit.removeString :
      if (pos.dnOffset == 0 && pos.dn.offsetLength == length) {
        // remove the whole text node
        dn = pos.dn;
        pos = new Position(dn.parent, dn.parent.offsetOf(dn));
      } else {
        text = pos.dn.nodeValue.substring(pos.dnOffset, pos.dnOffset + length);
      }
    }
    if (text != null) {
      assert(pos.dn.nodeType == DaxeNode.TEXT_NODE);
      pos.dn.remove(pos, text.length);
      if (update) {
        pos.dn.updateHTML();
        page.moveCursorTo(pos);
        if (pos.dn.previousSibling == null && pos.dn.nextSibling == null) {
          // the parent might be a simple type
          // NOTE: this might slow down text input
          pos.dn.parent.updateValidity();
        }
      }
    } else {
      dn.callBeforeRemove();
      DaxeNode parent = dn.parent;
      assert(parent != null);
      if (pos == null) {
        if (dn.previousSibling != null && dn.previousSibling.nodeType == DaxeNode.TEXT_NODE)
          pos = new Position(dn.previousSibling, dn.previousSibling.offsetLength);
        else
          pos = new Position(parent, parent.offsetOf(dn));
      }
      DaxeNode prev = dn.previousSibling;
      DaxeNode next = dn.nextSibling;
      parent.removeChild(dn);
      List<DaxeNode> childrenUpdate = new List<DaxeNode>();
      // we have to save the merged text node when the element is normalized
      if (prev != null && prev.nodeType == DaxeNode.TEXT_NODE && next != null &&
          next.nodeType == DaxeNode.TEXT_NODE) {
        cutNode = next;
        prev.nodeValue = "${prev.nodeValue}${next.nodeValue}";
        next.parent.removeChild(next);
        childrenUpdate.add(prev);
        childrenUpdate.add(dn);
        childrenUpdate.add(next);
      } else
        childrenUpdate.add(dn);
      if (update) {
        parent.updateValidity();
        parent.updateHTMLAfterChildrenChange(childrenUpdate);
        page.moveCursorTo(pos);
      }
    }
  }
  
  void _changeAttributes(bool update) {
    List<DaxeAttr> oldAttributes = dn.attributes;
    dn.attributes = attributes;
    attributes = oldAttributes;
    if (update) {
      dn.updateValidity();
      dn.updateAttributes();
    }
  }
  
  String toString() {
    StringBuffer sb = new StringBuffer();
    switch(operation) {
      case INSERT: sb.write("Insert "); break;
      case REMOVE: sb.write("Remove "); break;
      case ATTRIBUTES: sb.write("Attributes "); break;
      case COMPOUND: sb.write("Compound "); break;
    }
    if (text != null)
      sb.write("text '$text'");
    else if (dn != null)
      sb.write("node ${dn.nodeName}");
    else if (pos != null)
      sb.write("$length chars at $pos");
    return(sb.toString());
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/unknown_element_dialog.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/unknown_element_dialog.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

/**
 * Attribute dialog for an element that is not defined in the schema.
 */
class UnknownElementDialog {
  DaxeNode el;
  List<h.InputElement> nameInputs;
  List<h.InputElement> valueInputs;
  ActionFunction okfct;
  
  UnknownElementDialog(this.el, [this.okfct]) {
    nameInputs = new List<h.InputElement>();
    valueInputs = new List<h.InputElement>();
  }
  
  void show() {
    h.DivElement div1 = new h.DivElement();
    div1.id = 'attributes_dlg';
    div1.classes.add('dlg1');
    h.DivElement div2 = new h.DivElement();
    div2.classes.add('dlg2');
    h.DivElement div3 = new h.DivElement();
    div3.classes.add('dlg3');
    h.DivElement title = new h.DivElement();
    title.classes.add('dlgtitle');
    title.text = el.nodeName;
    div3.append(title);
    h.FormElement form = new h.FormElement();
    h.TableElement table = new h.TableElement();
    SimpleTypeControl toFocus = null;
    for (DaxeAttr att in el.attributes) {
      h.TableRowElement tr = new h.TableRowElement();
      h.TableCellElement td = new h.TableCellElement();
      h.InputElement nameInput = _newNameInput(att.name);
      nameInputs.add(nameInput);
      td.append(nameInput);
      tr.append(td);
      
      td = new h.TableCellElement();
      h.InputElement valueInput = _newValueInput(att.value);
      valueInputs.add(valueInput);
      td.append(valueInput);
      tr.append(td);
      
      _addRemoveButton(tr, nameInput);
      table.append(tr);
    }
    form.append(table);
    h.DivElement div_buttons = new h.DivElement();
    div_buttons.classes.add('buttons');
    h.ButtonElement bAdd = new h.ButtonElement();
    bAdd.attributes['type'] = 'button';
    bAdd.appendText(Strings.get("attribute.add"));
    bAdd.onClick.listen((h.MouseEvent event) => _addAttribute());
    div_buttons.append(bAdd);
    h.ButtonElement bCancel = new h.ButtonElement();
    bCancel.attributes['type'] = 'button';
    bCancel.appendText(Strings.get("button.Cancel"));
    bCancel.onClick.listen((h.MouseEvent event) => cancel());
    div_buttons.append(bCancel);
    h.ButtonElement bOk = new h.ButtonElement();
    bOk.attributes['type'] = 'submit';
    bOk.appendText(Strings.get("button.OK"));
    bOk.onClick.listen((h.MouseEvent event) => ok(event));
    div_buttons.append(bOk);
    form.append(div_buttons);
    div3.append(form);
    div2.append(div3);
    div1.append(div2);
    h.document.body.append(div1);
    if (toFocus != null)
      toFocus.focus();
  }
  
  void ok(h.MouseEvent event) {
    // save and close dialog
    List<DaxeAttr> attributes = new List<DaxeAttr>();
    for (int i=0; i<nameInputs.length; i++) {
      h.TextInputElement nameInput = nameInputs[i];
      String name = nameInput.value;
      if (!_isAttributeNameOK(name)) {
        event.preventDefault();
        //TODO: check if the bug with .focus() in Firefox is fixed
        nameInput.select();
        nameInput.selectionStart = nameInput.selectionEnd = nameInput.value.length;
        h.window.alert(Strings.get('attribute.invalid_attribute_name'));
        return;
      }
      String value = valueInputs[i].value;
      int ind = name.indexOf(':');
      DaxeAttr attribute;
      String namespace = doc.cfg != null ? doc.cfg.attributeNamespaceByName(name) : null;
      attribute = new DaxeAttr.NS(namespace, name, value);
      attributes.add(attribute);
    }
    h.querySelector('div#attributes_dlg').remove();
    event.preventDefault();
    if (el.getHTMLNode() != null) {
      UndoableEdit edit = new UndoableEdit.changeAttributes(el, attributes);
      doc.doNewEdit(edit);
    } else {
      // this is for a new element
      el.attributes = attributes;
    }
    page.focusCursor();
    if (okfct != null)
      okfct();
  }
  
  void cancel() {
    h.querySelector('div#attributes_dlg').remove();
    page.focusCursor();
  }
  
  void _addRemoveButton(tr, nameInput) {
    h.TableCellElement td = new h.TableCellElement();
    h.ButtonElement bMinus = new h.ButtonElement();
    bMinus.attributes['type'] = 'button';
    bMinus.value = '-';
    bMinus.text = '-';
    bMinus.onClick.listen((h.Event event) => _removeAttribute(nameInput));
    td.append(bMinus);
    tr.append(td);
  }
  
  h.TextInputElement _newNameInput([String name]) {
    h.TextInputElement nameInput = new h.TextInputElement();
    nameInput.spellcheck = false;
    nameInput.value = name != null ? name : "";
    nameInput.size = 20;
    // FIXME: this is not optimized:
    nameInput.onInput.listen((h.Event event) => _checkAttributeName(nameInput)); // onInput doesn't work with IE9 and backspace
    nameInput.onKeyUp.listen((h.KeyboardEvent event) => _checkAttributeName(nameInput)); // onKeyUp doesn't work with datalists
    return(nameInput);
  }
  
  bool _isAttributeNameOK(String name) {
    final RegExp r = new RegExp("^[^<>&#!/?'\",0-9.\\-\\s][^<>&#!/?'\",\\s]*\$"); // should be more restrictive
    return(r.hasMatch(name));
  }
  
  void _checkAttributeName(h.TextInputElement nameInput) {
    String name = nameInput.value;
    if (_isAttributeNameOK(name)) {
      nameInput.classes.add('valid');
      nameInput.classes.remove('invalid');
    } else {
      nameInput.classes.add('invalid');
      nameInput.classes.remove('valid');
    }
  }
  
  h.TextInputElement _newValueInput([String value]) {
    h.TextInputElement valueInput = new h.TextInputElement();
    valueInput.spellcheck = false;
    valueInput.value = value != null ? value : "";
    valueInput.size = 40;
    return(valueInput);
  }
  
  _addAttribute() {
    h.TableElement table = h.document.querySelector("#attributes_dlg table");
    h.TextInputElement nameInput = _newNameInput();
    h.TextInputElement valueInput = _newValueInput();
    nameInputs.add(nameInput);
    valueInputs.add(valueInput);
    h.TableRowElement newtr = new h.TableRowElement();
    h.TableCellElement td = new h.TableCellElement();
    td.append(nameInput);
    newtr.append(td);
    td = new h.TableCellElement();
    td.append(valueInput);
    newtr.append(td);
    _addRemoveButton(newtr, nameInput);
    table.append(newtr);
  }
  
  void _removeAttribute(h.InputElement nameInput) {
    for (int i=0; i<nameInputs.length; i++) {
      if (nameInputs[i] == nameInput) {
        nameInputs.removeAt(i);
        valueInputs.removeAt(i);
        h.TableRowElement tr = h.document.querySelector("#attributes_dlg table tr:nth-child(${i+1})");
        tr.remove();
      }
    }
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/validation_dialog.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/validation_dialog.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

/**
 * Dialog with a list of XML validation errors.
 * A click on an error displays the matching element.
 */
class ValidationDialog {
  
  void show() {
    h.DivElement div1 = new h.DivElement();
    div1.id = 'dlg1';
    div1.classes.add('dlg1');
    h.DivElement div2 = new h.DivElement();
    div2.classes.add('dlg2');
    h.DivElement div3 = new h.DivElement();
    div3.classes.add('dlg3');
    h.DivElement title = new h.DivElement();
    title.classes.add('dlgtitle');
    title.text = Strings.get('validation.validation');
    div3.append(title);
    
    List<DaxeNode> invalid = invalidElements(doc.dndoc);
    if (invalid.length == 0) {
      div3.appendText(Strings.get('validation.no_error'));
    } else {
      h.DivElement div4 = new h.DivElement();
      div4.style.maxHeight = '30em';
      div4.style.overflow = 'auto';
      div4.appendText(Strings.get('validation.errors'));
      h.UListElement ul = new h.UListElement();
      for (DaxeNode dn in invalid) {
        h.LIElement li = new h.LIElement();
        String title;
        if (dn.ref != null)
          title = doc.cfg.elementTitle(dn.ref);
        else
          title = null;
        Position pos = new Position(dn, 0);
        String itemText;
        if (title != null)
          itemText = "$title ${pos.xPath()}";
        else
          itemText = pos.xPath();
        li.appendText(itemText);
        li.onClick.listen((h.MouseEvent event) => select(dn));
        li.style.cursor = 'default';
        ul.append(li);
      }
      div4.append(ul);
      div3.append(div4);
    }
    
    h.DivElement div_buttons = new h.DivElement();
    div_buttons.classes.add('buttons');
    h.ButtonElement bOk = new h.ButtonElement();
    bOk.attributes['type'] = 'submit';
    bOk.appendText(Strings.get("button.Close"));
    bOk.onClick.listen((h.MouseEvent event) => close());
    div_buttons.append(bOk);
    div3.append(div_buttons);
    
    div2.append(div3);
    div1.append(div2);
    h.document.body.append(div1);
    bOk.focus();
  }
  
  List<DaxeNode> invalidElements(DaxeNode dn) {
    List<DaxeNode> invalid = new List<DaxeNode>();
    if (dn.nodeType == DaxeNode.ELEMENT_NODE && dn is! DNCData) {
      // note: empty DNForm nodes are ignored unless they are required
      if (dn.parent is DNForm) {
        bool required = doc.cfg.requiredElement(dn.parent.ref, dn.ref);
        if (required && dn.firstChild == null) {
          invalid.add(dn);
          return(invalid);
        } else if (!required && dn.firstChild == null && (dn.attributes == null || dn.attributes.length == 0)) {
          return(invalid);
        }
      }
      if (!doc.cfg.elementIsValid(dn))
        invalid.add(dn);
    }
    for (DaxeNode child=dn.firstChild; child != null; child=child.nextSibling)
      invalid.addAll(invalidElements(child));
    
    return(invalid);
  }
  
  void select(DaxeNode dn) {
    h.DivElement div1 = h.document.getElementById('dlg1');
    div1.remove();
    page.selectNode(dn);
  }
  
  void close() {
    h.DivElement div1 = h.document.getElementById('dlg1');
    div1.remove();
    page.focusCursor();
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/web_page.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/web_page.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of daxe;

/**
 * Represents a web page (window or tab). A page can only contain a single document.
 */
class WebPage {
  static const int doubleClickTime = 400; // maximum time between the clicks, in milliseconds
  Cursor _cursor;
  Position selectionStart, selectionEnd;
  MenuBar mbar;
  MenuItem undoMenu, redoMenu;
  Menu contextualMenu;
  h.Point ctxMenuPos;
  Toolbar toolbar;
  LeftPanel _left;
  Position lastClickPosition;
  DateTime lastClickTime;
  bool selectionByWords; // double-click+drag
  
  WebPage() {
    _cursor = new Cursor();
    _left = new LeftPanel();
    lastClickPosition = null;
    lastClickTime = null;
    selectionByWords = false;
  }
  
  Future newDocument(String configPath) {
    Completer completer = new Completer();
    doc.newDocument(configPath).then( (_) {
      _buildMenus();
      init();
      _left.selectInsertPanel();
      h.document.title = Strings.get('page.new_document');
      completer.complete();
    }, onError: (DaxeException ex) {
      h.Element divdoc = h.document.getElementById('doc2');
      String msg = "Error creating the new document: $ex";
      divdoc.text = msg;
      completer.completeError(msg);
    });
    return(completer.future);
  }
  
  Future openDocument(String filePath, String configPath, {bool removeIndents: true}) {
    Completer completer = new Completer();
    doc.openDocument(filePath, configPath).then( (_) {
      _buildMenus();
      init();
      _left.selectTreePanel();
      doc.dndoc.callAfterInsert();
      h.document.title = filePath.split('/').last;
      completer.complete();
    }, onError: (DaxeException ex) {
      h.Element divdoc = h.document.getElementById('doc2');
      String msg = "Error reading the document: $ex";
      divdoc.text = msg;
      completer.completeError(msg);
    });
    return(completer.future);
  }
  
  void init() {
    h.Element divdoc = h.document.getElementById('doc2');
    divdoc.children.clear();
    h.document.body.insertBefore(_left.html(), h.document.getElementById('doc1'));
    
    // adjust positions when the toolbar is on more than 1 lines
    // (this cannot be done with CSS)
    adjustPositionsUnderToolbar();
    h.window.onResize.listen((h.Event event) => adjustPositionsUnderToolbar());
    
    // insert document content
    h.Element elhtml = doc.html();
    divdoc.append(elhtml);
    
    Position pos = new Position(doc.dndoc, 0);
    _cursor.moveTo(pos);
    updateAfterPathChange();
    
    divdoc.onMouseDown.listen((h.MouseEvent event) => onMouseDown(event));
    divdoc.onMouseMove.listen((h.MouseEvent event) => onMouseMove(event));
    divdoc.onMouseUp.listen((h.MouseEvent event) => onMouseUp(event));
    divdoc.onContextMenu.listen((h.MouseEvent event) => onContextMenu(event));
    h.document.getElementById('doc1').onScroll.listen((h.Event event) => onScroll(event));
    h.document.onMouseUp.listen((h.MouseEvent event) {
      if (contextualMenu != null) {
        h.Element div = contextualMenu.getMenuHTMLNode();
        if (div.scrollHeight > div.clientHeight && event.target == div &&
            event.client.x > div.offsetLeft + div.clientWidth)
          return; // in the scrollbar
        if (event.client != ctxMenuPos)
          closeContextualMenu();
        event.preventDefault();
      }
    });
  }
  
  void adjustPositionsUnderToolbar() {
    h.Element headers = h.document.getElementById('headers');
    num y = headers.getBoundingClientRect().bottom;
    String cssTop = (y.round() + 2).toString() + "px";
    h.document.getElementById('left_panel').style.top = cssTop;
    h.document.getElementById('doc1').style.top = cssTop;
  }
  
  void _buildMenus() {
    mbar = doc.cfg.makeMenus(doc);
    Menu fileMenu = new Menu(Strings.get('menu.file'));
    fileMenu.parent = mbar;
    MenuItem item;
    if (doc.saveURL != null) {
      item = new MenuItem(Strings.get('menu.save'), () => save(), shortcut: 'S');
      fileMenu.add(item);
    }
    item = new MenuItem(Strings.get('menu.source'), () => showSource());
    fileMenu.add(item);
    item = new MenuItem(Strings.get('menu.validation'), () => (new ValidationDialog()).show());
    fileMenu.add(item);
    mbar.insert(fileMenu, 0);
    Menu editMenu = new Menu(Strings.get('menu.edit'));
    editMenu.parent = mbar;
    undoMenu = new MenuItem(Strings.get('undo.undo'), () => doc.undo(), shortcut: 'Z');
    undoMenu.enabled = false;
    editMenu.add(undoMenu);
    redoMenu = new MenuItem(Strings.get('undo.redo'), () => doc.redo(), shortcut: 'Y');
    redoMenu.enabled = false;
    editMenu.add(redoMenu);
    editMenu.add(new MenuItem(Strings.get('menu.select_all'), () => selectAll(), shortcut: 'A'));
    MenuItem findMenu = new MenuItem(Strings.get('find.find_replace'), () => (new FindDialog()).show(), shortcut: 'F');
    editMenu.add(findMenu);
    mbar.insert(editMenu, 1);
    
    h.Element headers = h.document.getElementById('headers');
    headers.append(mbar.html());
    
    toolbar = new Toolbar(doc.cfg);
    headers.append(toolbar.html());
    HashMap<String, ActionFunction> shortcuts = new HashMap<String, ActionFunction>();
    for (ToolbarButton button in toolbar.buttons)
      if (button.shortcut != null)
        shortcuts[button.shortcut] = button.action;
    
    for (Menu menu in mbar.menus) {
      addMenuShortcuts(menu, shortcuts);
    }
    _cursor.setShortcuts(shortcuts);
  }
  
  addMenuShortcuts(Menu menu, HashMap<String, ActionFunction> shortcuts) {
    for (MenuItem item in menu.items) {
      if (item.shortcut != null && item.action != null)
        shortcuts[item.shortcut] = item.action;
      if (item is Menu)
        addMenuShortcuts(item, shortcuts);
    }
  }
  
  void onMouseDown(h.MouseEvent event) {
    if (contextualMenu != null)
      closeContextualMenu();
    // do not stop event propagation in some cases:
    if (event.target is h.ImageElement ||
        event.target is h.ButtonElement ||
        event.target is h.TextInputElement ||
        event.target is h.SelectElement)
      return;
    h.Element parent = event.target;
    while (parent is h.Element && !parent.classes.contains('dn')) {
      parent = parent.parent;
    }
    if (parent != null && parent.attributes['contenteditable'] == 'true')
      return;
    if (event.button == 1)
      return;
    
    event.preventDefault();
    if (event.button == 2) {
      // this is handled in onContextMenu
      return;
    }
    if (event.shiftKey) {
      selectionStart = new Position.clone(_cursor.selectionStart);
      selectionEnd = Cursor.findPosition(event);
      if (selectionEnd != null)
        _cursor.setSelection(selectionStart, selectionEnd);
    } else {
      selectionStart = Cursor.findPosition(event);
      if (selectionStart != null && lastClickPosition == selectionStart &&
          lastClickTime.difference(new DateTime.now()).inMilliseconds.abs() < doubleClickTime &&
          selectionStart.dn.nodeType != DaxeNode.ELEMENT_NODE) {
        // double click
        List<Position> positions = _extendPositionOnWord(selectionStart);
        selectionStart = positions[0];
        selectionEnd = positions[1];
        _cursor.setSelection(selectionStart, selectionEnd);
        selectionByWords = true;
      }
    }
  }
  
  void onMouseMove(h.MouseEvent event) {
    if (selectionStart == null)
      return;
    if (contextualMenu != null)
      return;
    Position newpos = Cursor.findPosition(event);
    if (selectionByWords) {
      if (selectionEnd > selectionStart && newpos <= selectionStart)
        selectionStart = selectionEnd;
      else if (selectionEnd < selectionStart && newpos >= selectionStart)
        selectionStart = selectionEnd;
    }
    selectionEnd = newpos;
    if (selectionStart != null && selectionEnd != null) {
      if (selectionByWords && selectionEnd.dn.nodeType != DaxeNode.ELEMENT_NODE) {
        List<Position> positions = _extendPositionOnWord(selectionEnd);
        if (selectionEnd > selectionStart)
          selectionEnd = positions[1];
        else
          selectionEnd = positions[0];
      }
      _cursor.setSelection(selectionStart, selectionEnd);
    }
    event.preventDefault();
  }
  
  List<Position> _extendPositionOnWord(Position pos) {
    List<Position> positions = new List<Position>();
    String s = pos.dn.nodeValue;
    int i1 = pos.dnOffset;
    int i2 = pos.dnOffset;
    String wstop = ' \n,;:.?!/()[]{}';
    while (i1 > 0 && wstop.indexOf(s[i1-1]) == -1)
      i1--;
    while (i2 < s.length && wstop.indexOf(s[i2]) == -1)
      i2++;
    positions.add(new Position(pos.dn, i1));
    positions.add(new Position(pos.dn, i2));
    return(positions);
  }
  
  void onMouseUp(h.MouseEvent event) {
    /*
     this interferes with selection, why was it there again ?
     if (event.target is h.ImageElement ||
        event.target is h.ButtonElement ||
        event.target is h.TextInputElement ||
        event.target is h.SelectElement)
      return;
    */
    if (!selectionByWords)
      selectionEnd = Cursor.findPosition(event);
    lastClickPosition = null;
    if (selectionStart != null && selectionEnd != null) {
      if (!selectionByWords)
        _cursor.setSelection(selectionStart, selectionEnd);
      if (selectionStart == selectionEnd) {
        lastClickPosition = selectionStart;
        lastClickTime = new DateTime.now();
      }
    }
    selectionStart = null;
    selectionEnd = null;
    selectionByWords = false;
    event.preventDefault();
  }
  
  void onContextMenu(h.MouseEvent event) {
    if (event.shiftKey)
      return;
    Position newpos = Cursor.findPosition(event);
    if (newpos != null) {
      event.preventDefault();
      if (_cursor.selectionStart == null || _cursor.selectionEnd == null ||
          (newpos < _cursor.selectionStart && newpos < _cursor.selectionEnd) ||
          (newpos > _cursor.selectionStart && newpos > _cursor.selectionEnd)) {
        _cursor.setSelection(newpos, newpos);
      }
      if (_cursor.selectionStart != null)
        showContextualMenu(event);
    }
  }
  
  void onScroll(h.Event event) {
    _cursor.updateCaretPosition(false);
  }
  
  void charInsert(Position pos, String s) {
    doc.insertString(pos, s);
  }
  
  void showContextualMenu(h.MouseEvent event) {
    if (doc.cfg == null || _cursor.selectionStart.dn == null)
      return;
    ctxMenuPos = event.client;
    DaxeNode parent;
    if (_cursor.selectionStart.dn is DNText)
      parent = _cursor.selectionStart.dn.parent;
    else
      parent = _cursor.selectionStart.dn;
    List<x.Element> refs = doc.elementsAllowedUnder(parent);
    List<x.Element> validRefs = doc.validElementsInSelection(refs);
    contextualMenu = new Menu(null);
    bool addSeparator = false;
    if (parent.ref != null) {
      String elementTitle = doc.cfg.menuTitle(parent.nodeName);
      String title = "${Strings.get('contextual.select_element')} $elementTitle";
      contextualMenu.add(new MenuItem(title, () => selectNode(parent)));
      List<x.Element> attRefs = doc.cfg.elementAttributes(parent.ref);
      if (attRefs != null && attRefs.length > 0) {
        title = "${Strings.get('contextual.edit_attributes')} $elementTitle";
        contextualMenu.add(new MenuItem(title, () =>
            parent.attributeDialog()));
      }
      title = "${Strings.get('contextual.help_about_element')} $elementTitle";
      contextualMenu.add(new MenuItem(title, () =>
          (new HelpDialog.Element(parent.ref)).show()));
      addSeparator = true;
    }
    if (doc.hiddendiv != null) {
      DaxeNode dndiv = parent;
      while (dndiv != null && dndiv is! DNHiddenDiv)
        dndiv = dndiv.parent;
      if (dndiv != null) {
        if (addSeparator)
          contextualMenu.addSeparator();
        String elementTitle = doc.cfg.menuTitle(dndiv.nodeName);
        List<x.Element> attRefs = doc.cfg.elementAttributes(dndiv.ref);
        if (attRefs != null && attRefs.length > 0) {
          String title = "${Strings.get('contextual.edit_attributes')} $elementTitle";
          contextualMenu.add(new MenuItem(title, () =>
              dndiv.attributeDialog()));
        }
        contextualMenu.add(new MenuItem(Strings.get('div.remove'), () =>
            (dndiv as DNHiddenDiv).removeDiv()));
        addSeparator = true;
      }
    }
    List<x.Element> toolbarRefs;
    if (toolbar != null)
      toolbarRefs = toolbar.elementRefs();
    else
      toolbarRefs = null;
    bool first = true;
    for (x.Element ref in validRefs) {
      if (toolbarRefs != null && toolbarRefs.contains(ref))
        continue;
      if (doc.hiddenParaRefs != null && doc.hiddenParaRefs.contains(ref))
        continue;
      if (first && addSeparator)
        contextualMenu.addSeparator();
      first = false;
      String name = doc.cfg.elementName(ref);
      String title = doc.cfg.menuTitle(name);
      MenuItem item = new MenuItem(title, () => doc.insertNewNode(ref, 'element'));
      contextualMenu.add(item);
    }
    h.DivElement div = contextualMenu.htmlMenu();
    div.style.position = 'fixed';
    div.style.display = 'block';
    int xpos = event.client.x;
    int ypos = event.client.y;
    div.style.left = "${xpos}px";
    div.style.top = "${ypos}px";
    div.style.maxHeight = "${h.window.innerHeight - ypos}px";
    div.style.overflowY = 'auto';
    h.document.body.append(div);
    if (div.scrollHeight > div.clientHeight)
      div.style.paddingRight = "${div.offsetWidth - div.clientWidth}px"; // for the vertical scrollbar
    if (xpos + div.offsetWidth > h.window.innerWidth) {
      xpos = h.window.innerWidth - div.offsetWidth;
      div.style.left = "${xpos}px";
    }
  }
  
  void closeContextualMenu() {
    h.DivElement div = contextualMenu.getMenuHTMLNode();
    div.remove();
    contextualMenu = null;
    ctxMenuPos = null;
  }
  
  
  /**
   * Returns the Daxe node containing the HTML node
   */
  @deprecated
  DaxeNode getDaxeNode(h.Node n) {
    if (n == null)
      return(null);
    h.Element el;
    if (n.nodeType == DaxeNode.TEXT_NODE)
      el = n.parent;
    else if (n.nodeType == DaxeNode.ELEMENT_NODE)
      el = n;
    else
      return(null);
    DaxeNode jn = doc.getNodeById(el.attributes['id']);
    if (jn == null)
      return(getDaxeNode(el.parent));
    return(jn);
  }
  
  Cursor get cursor {
    return(_cursor);
  }
  
  Position getSelectionStart() {
    return(_cursor.selectionStart);
  }
  
  Position getSelectionEnd() {
    return(_cursor.selectionEnd);
  }
  
  void moveCursorTo(Position pos) {
    _cursor.moveTo(pos);
  }
  
  void focusCursor() {
    _cursor.focus();
  }
  
  void scrollToNode(DaxeNode dn) {
    Position startPos = new Position(dn.parent, dn.parent.offsetOf(dn));
    Point pt = startPos.positionOnScreen();
    if (pt == null)
      return;
    h.DivElement doc1 = h.document.getElementById('doc1'); 
    int doctop = doc1.offset.top;
    doc1.scrollTop += pt.y.toInt() - doctop - 10;
  }
  
  void selectNode(DaxeNode dn) {
    int offset = dn.parent.offsetOf(dn);
    Position p1 = new Position(dn.parent, offset);
    Position p2 = new Position(dn.parent, offset+1);
    _cursor.moveTo(p1); // to scroll
    _cursor.setSelection(p1, p2);
    updateAfterPathChange();
  }
  
  void selectAll() {
    _cursor.setSelection(new Position(doc.dndoc, 0), new Position(doc.dndoc, doc.dndoc.offsetLength));
  }
  
  void updateAfterPathChange() {
    if (_cursor.selectionStart == null)
      return;
    DaxeNode parent = _cursor.selectionStart.dn;
    if (parent is DNText)
      parent = parent.parent;
    List<x.Element> refs = doc.elementsAllowedUnder(parent);
    List<x.Element> validRefs = doc.validElementsInSelection(refs);
    _left.update(parent, refs, validRefs);
    updateMenusAndToolbar(parent, validRefs);
    updatePath();
  }
  
  void updateMenusAndToolbar(DaxeNode parent, List<x.Element> validRefs) {
    List<Menu> menus = mbar.menus;
    for (Menu m in menus) {
      _updateMenu(m, validRefs);
    }
    if (toolbar != null)
      toolbar.update(parent, validRefs);
  }
  
  void _updateMenu(Menu m, List<x.Element> validRefs) {
    for (MenuItem item in m.items) {
      if (item is Menu) {
        _updateMenu(item, validRefs);
      } else if (item.data is x.Element) {
        x.Element ref = item.data;
        String elementName = doc.cfg.elementName(ref);
        bool found = false;
        for (x.Element vref in validRefs) {
          if (doc.cfg.elementName(vref) == elementName) {
            found = true;
            if (vref != ref) {
              // this can happen if 2 different elements have the same name
              item.data = vref;
              item.action = () => doc.insertNewNode(vref, 'element');
            }
            break;
          }
        }
        if (found)
          item.enable();
        else
          item.disable();
        
      }
    }
  }
  
  void updatePath() {
    h.Element div_path = h.document.getElementById('path');
    if (_cursor.selectionStart == null)
      div_path.text = "";
    else
      div_path.text = _cursor.selectionStart.xPath(titles:true);
  }
  
  void updateUndoMenus() {
    if (doc.isUndoPossible()) {
      if (!undoMenu.enabled)
        undoMenu.enable();
    } else {
      if (undoMenu.enabled)
        undoMenu.disable();
    }
    if (doc.isRedoPossible()) {
      if (!redoMenu.enabled)
        redoMenu.enable();
    } else {
      if (redoMenu.enabled)
        redoMenu.disable();
    }
    undoMenu.title = doc.getUndoTitle();
    redoMenu.title = doc.getRedoTitle();
    if (toolbar != null) {
      for (ToolbarButton button in toolbar.buttons) {
        if (button.data == "undo") {
          if (doc.isUndoPossible())
            button.enable();
          else
            button.disable();
          button.title = doc.getUndoTitle();
        } else if (button.data == "redo") {
          if (doc.isRedoPossible())
            button.enable();
          else
            button.disable();
          button.title = doc.getRedoTitle();
        }
      }
    }
  }
  
  void save() {
    doc.saveOnWebJaxe().then((_) {
      h.window.alert(Strings.get('save.success'));
    }, onError: (DaxeException ex) {
      h.window.alert(Strings.get('save.error') + ': ' + ex.message);
    });
  }
  
  void showSource() {
    //String data = encodeUriComponent(doc.toString());
    // encodeUriComponent adds + instead of whitespace
    //data = data.replaceAll('+', '%20');
    //This data URI does not work with IE9
    //h.WindowBase popup = h.window.open('data:text/xml;charset=UTF-8,$data', 'source');
    
    (new SourceWindow()).show();
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/equations/equation_dialog.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/equations/equation_dialog.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of equations;

class EquationDialog {
  String _equationText;
  String _labelName;
  String _labelValue;
  ActionFunction _okfct;
  MathBase _base;
  
  
  EquationDialog(String equationText, {String labelName, String labelValue, ActionFunction okfct}) {
    _equationText = equationText;
    _labelName = labelName;
    _labelValue = labelValue;
    _okfct = okfct;
  }
  
  void show() {
    h.DivElement div1 = new h.DivElement();
    div1.id = 'dlg1';
    div1.classes.add('dlg1');
    h.DivElement div2 = new h.DivElement();
    div2.classes.add('dlg2');
    h.DivElement div3 = new h.DivElement();
    div3.classes.add('dlg3');
    h.FormElement form = new h.FormElement();
    
    h.CanvasElement canvas = new h.CanvasElement(width:500, height: 300);
    canvas.id = 'eqcanvas';
    StringMathBuilder sb = new StringMathBuilder(_equationText);
    _base = new MathBase(element:sb.getMathRootElement(), context:canvas.context2D);
    _base.paint(canvas.context2D);
    form.append(canvas);
    
    h.TextAreaElement ta = new h.TextAreaElement();
    ta.id = 'eqtext';
    ta.value = _equationText;
    ta.style.width = '100%';
    ta.style.height = '4em';
    ta.attributes['spellcheck'] = 'false';
    ta.onInput.listen((h.Event event) => updateDisplay());
    form.append(ta);
    
    if (_labelName != null) {
      h.DivElement div_label = new h.DivElement();
      h.SpanElement label_name = new h.SpanElement();
      label_name.text = _labelName;
      div_label.append(label_name);
      div_label.appendText(' ');
      h.TextInputElement input_label = new h.TextInputElement();
      input_label.id = 'eqlabel';
      if (_labelValue != null)
        input_label.value = _labelValue;
      div_label.append(input_label);
      form.append(div_label);
    }
    
    h.DivElement div_buttons = new h.DivElement();
    div_buttons.classes.add('buttons');
    h.ButtonElement bCancel = new h.ButtonElement();
    bCancel.attributes['type'] = 'button';
    bCancel.appendText(Strings.get("button.Cancel"));
    bCancel.onClick.listen((h.MouseEvent event) => div1.remove());
    div_buttons.append(bCancel);
    h.ButtonElement bOk = new h.ButtonElement();
    bOk.attributes['type'] = 'submit';
    bOk.appendText(Strings.get("button.OK"));
    bOk.onClick.listen((h.MouseEvent event) => ok(event));
    div_buttons.append(bOk);
    form.append(div_buttons);
    
    div3.append(form);
    div2.append(div3);
    div1.append(div2);
    h.document.body.append(div1);
    
    ta.focus();
  }
  
  void ok(h.MouseEvent event) {
    h.TextAreaElement ta = h.querySelector('textarea#eqtext');
    _equationText = ta.value;
    if (_labelName != null) {
      h.TextInputElement input_label = h.querySelector('input#eqlabel');
      _labelValue = input_label.value;
    }
    h.querySelector('div#dlg1').remove();
    if (event != null)
      event.preventDefault();
    if (_okfct != null)
      _okfct();
  }
  
  String getText() {
    return(_equationText);
  }
  
  String getData() {
    if (_equationText == '')
      return(null);
    h.CanvasElement canvas2 = new h.CanvasElement(width:_base.getWidth(), height:_base.getHeight());
    StringMathBuilder sb = new StringMathBuilder(_equationText);
    MathBase base2 = new MathBase(element:sb.getMathRootElement(), context:canvas2.context2D);
    base2.paint(canvas2.context2D);
    String dataurl = canvas2.toDataUrl('image/png');
    String data = dataurl.substring('data:image/png;base64,'.length);
    return(data);
  }
  
  String getLabel() {
    return(_labelValue);
  }
  
  void updateDisplay() {
    h.TextAreaElement ta = h.querySelector('textarea#eqtext');
    _equationText = ta.value;
    if (_equationText.length > 0 && _equationText.contains('\n')) {
      ta.value = _equationText.replaceAll('\n', '');
      ok(null);
      return;
    }
    h.CanvasElement canvas = h.querySelector('canvas#eqcanvas');
    StringMathBuilder sb = new StringMathBuilder(_equationText);
    _base.setRootElement(sb.getMathRootElement());
    _base.paint(canvas.context2D);
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/equations/equations.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/equations/equations.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

/**
 * Implementation for Jaxe style equations.
 * Syntax documented [here](http://jaxe.sourceforge.net/en/pages_jaxe-user-guide/equations.html).
 */
library equations;

//import 'package:meta/meta.dart';
import 'dart:html' as h;
import 'dart:math';

import '../strings.dart';

part 'equation_dialog.dart';
part 'math_base.dart';
part 'string_math_builder.dart';
part 'text_metrics.dart';

part 'elements/math_element.dart';
part 'elements/math_frac.dart';
part 'elements/math_identifier.dart';
part 'elements/math_number.dart';
part 'elements/math_operator.dart';
part 'elements/math_over.dart';
part 'elements/math_phantom.dart';
part 'elements/math_root.dart';
part 'elements/math_root_element.dart';
part 'elements/math_row.dart';
part 'elements/math_sqrt.dart';
part 'elements/math_sub.dart';
part 'elements/math_sub_sup.dart';
part 'elements/math_sup.dart';
part 'elements/math_table.dart';
part 'elements/math_table_data.dart';
part 'elements/math_table_row.dart';
part 'elements/math_text.dart';
part 'elements/math_under.dart';
part 'elements/math_under_over.dart';

typedef void ActionFunction();



Index: modules/damieng/graphical_editor/daxe/lib/src/equations/math_base.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/equations/math_base.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of equations;

/**
 * The base for creating a MathElement tree. Based on STIX fonts.
 */
class MathBase {
  static String STIXFontRegular = 'STIXSubset-Regular';
  static String STIXFontItalic = 'STIXSubset-Italic';
  static String STIXFontBold = 'STIXSubset-Bold';
  static bool fontsLoaded = false;
  
  static const int STYLE_PLAIN = 0;
  static const int STYLE_BOLD = 1;
  static const int STYLE_ITALIC = 2;
  static const int STYLE_BOLD_ITALIC = 3;
  
  int _inlinefontsize;
  int _displayfontsize;

  final int minfontsize = 8;
  final int maxfontsize = 60;

  List<TextMetrics> _fontmetrics = null;

  bool _debug = false;

  /** Inline mathematical expression */
  static final int INLINE = 0;

  /** Non inline mathematical expression */
  static final int DISPLAY = 1;

  final int _mode = INLINE;

  MathRootElement _rootElement;
  
  h.CanvasRenderingContext2D _context;
  
  
  /**
   * Creates a MathBase
  *
   * @param element Root element of a math tree
   * @param inlinefontsize Size of the preferred font used by inline equations
   * @param displayfontsize Size of the preferred font used by non inline equations
   * @param gcalc Graphics object to use to calculate character sizes (nothing will be painted on it)
   */
  MathBase({final MathRootElement element, final int inlinefontsize:15, final int displayfontsize:16,
      final h.CanvasRenderingContext2D context}) {
    
    this._inlinefontsize = inlinefontsize;
    this._displayfontsize = displayfontsize;
    
    if (context != null)
      _fontmetrics = new List<TextMetrics>(maxfontsize);
    if (element != null)
      setRootElement(element);
    if (context != null)
      this._context = context;
  }
  
  /**
   * This should be called in advance, so that the browser is reader
   * to use the fonts when it is needed.
   */
  static void loadFonts() {
    if (fontsLoaded)
      return;
    h.CanvasElement canvas = new h.CanvasElement(width:10, height: 10);
    h.CanvasRenderingContext2D context = canvas.context2D;
    context.font = "15px $STIXFontRegular";
    context.fillText('.', 0, 0);
    context.font = "15px $STIXFontItalic";
    context.fillText('.', 0, 0);
    context.font = "15px $STIXFontBold";
    context.fillText('.', 0, 0);
    fontsLoaded = true;
  }
  
  /**
   * Set the root element of a math tree
  *
   * @param element Root element of a math tree
   */
  void setRootElement(final MathRootElement element) {
    if (element == null)
      return;
    
    _rootElement = element;
    
    _rootElement.setMathBase(this);
    
    if (element.getMode() == MathRootElement.DISPLAY)
      _rootElement.setFontSize(_displayfontsize);
    else
      _rootElement.setFontSize(_inlinefontsize);
    
    _rootElement.setDebug(isDebug());
  }

  /**
   * Enables, or disables the debug mode
   *
   * @param debug Debug mode
   */
  void setDebug(final bool debug) {
    this._debug = debug;
    if (_rootElement != null)
      _rootElement.setDebug(debug);
  }

  /**
   * Indicates, if the debug mode is enabled
   *
   * @return True, if the debug mode is enabled
   */
  bool isDebug() {
    return _debug;
  }

  /**
   * Sets the default font size, which used for the root element
   *
   * @param fontsize Font size
   */
  void setDefaultFontSize(final int fontsize) {
    if (fontsize >= minfontsize || fontsize < maxfontsize)
      this._inlinefontsize = fontsize;
  }

  /**
   * Get the default font size
   *
   * @return Default font size
   */
  int getDefaultInlineFontSize() {
    return _inlinefontsize;
  }

  /**
   * Sets the default font size for non inline equations
   *
   * @param fontsize Default font size
   */
  void setDefaultDisplayFontSize(final int fontsize) {
    if (fontsize >= minfontsize || fontsize < maxfontsize)
      this._displayfontsize = fontsize;
  }

  /**
   * Get the default font size for non inline equations
   *
   * @return Default display font size
   */
  int getDefaultDisplayFontSize() {
    return _displayfontsize;
  }

  /**
   * Get a font specified by the font size
   *
   * @param fontsize Font size
   *
   * @return Font
   */
  String getFont(final int fontsize, [int style=MathBase.STYLE_PLAIN]) {
    int size;
    if (fontsize < minfontsize)
      size = minfontsize;
    else if (fontsize > maxfontsize)
      size = maxfontsize;
    else
      size = fontsize;
    String sstyle, sfont;
    if (style == MathBase.STYLE_ITALIC) {
      sstyle = '';
      sfont = STIXFontItalic;
    } else if (style == MathBase.STYLE_BOLD) {
      sstyle = '';
      sfont = STIXFontBold;
    } else if (style == MathBase.STYLE_BOLD_ITALIC) {
      sstyle = 'italic';
      sfont = STIXFontBold;
    } else {
      sstyle = '';
      sfont = STIXFontRegular;
    }
    return("$sstyle ${size}px $sfont");
  }

  /**
   * Get the font metrics specified by the font size
   *
   * @param fontsize Font size
   *
   * @return Font metrics
   */
  TextMetrics getFontMetrics(final int fontsize) {
    int size;
    if (fontsize < minfontsize)
      size = minfontsize;
    else if (fontsize > maxfontsize)
      size = maxfontsize;
    else
      size = fontsize;
    if (_fontmetrics[size] == null)
      _fontmetrics[size] = createFontMetrics(getFont(fontsize));
    return _fontmetrics[size];
  }

  TextMetrics createFontMetrics(String font) {
    return(new TextMetrics('Hg', font));
  }
  
  double stringWidth(String s, String font) {
    // note: we could also use TextMetrics.width, it would be more consistent but maybe less precise...
    String save = _context.font;
    _context.font = font;
    double width = _context.measureText(s).width;
    _context.font = save;
    return(width);
  }
  
  /**
   * Paints this component and all of its elements
   *
   * @param context The graphics context to use for painting
   */
  void paint(final h.CanvasRenderingContext2D context) {
    context.clearRect(0, 0, context.canvas.width, context.canvas.height);
    if (_rootElement != null)
      _rootElement.paintComponent(context);
  }

  /**
   * Return the current width of this component
   *
   * @return Width
   */
  int getWidth() {
    if (_rootElement != null && _fontmetrics != null)
      return _rootElement.getComponentWidth();
    return 0;
  }

  /**
   * Return the current height of this component
   *
   * @return Height
   */
  int getHeight() {
    if (_rootElement != null && _fontmetrics != null)
      return _rootElement.getComponentHeight();
    return 0;
  }
  
}


Index: modules/damieng/graphical_editor/daxe/lib/src/equations/string_math_builder.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/equations/string_math_builder.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of equations;

/**
 * Parser for equations with Jaxe's syntax.
 * The parsing happens in two steps :
 * - parsing of the string, with the creation of in-memory structures representing the mathematical meaning of the equation
 * - transformation into presentational MathML
 */
class StringMathBuilder {

  MathRootElement _rootElement;
  
  // opérateurs remplacés en premier, avant l'analyse de la syntaxe
  static final List<List<String>> special = [
     ["<==", "\u21D0"], ["==>", "\u21D2"], ["<=>", "\u21D4"],
     ["!=", "\u2260"], ["~=", "\u2248"], ["~", "\u223C"],
     ["<=", "\u2264"], [">=", "\u2265"], ["<<", "\u226A"], [">>", "\u226B"],
     //  "->" kepts for backward-compatibility
     ["-->", "\u2192"], ["<->", "\u2194"], ["->", "\u2192"], ["<--", "\u2190"],
     ["equiv", "\u2261"],
     ["forall", "\u2200"], ["quelquesoit", "\u2200"],
     ["exists", "\u2203"], ["ilexiste", "\u2203"],
     ["part", "\u2202"], ["drond", "\u2202"],
     ["nabla", "\u2207"],
     ["prop", "\u221D"],
     ["times", "×"], ["cross", "×"], ["croix", "×"],
     ["wedge", "\u2227"], ["pvec", "\u2227"],
     ["plusmn", "±"], ["plusoumoins", "±"], ["plusminus", "±"],
     ["cap", "\u2229"], ["cup", "\u222A"],
     ["...", "\u2026"]
  ];
  
  // opérateurs
  static final String sops = 
      "_^#*/\u2207±\u2213\u2227-+\u2200\u2203\u2202×=\u2260\u2248\u223C\u2261<>\u2264\u2265\u226A\u226B\u221D" +
      "|\u2229\u222A\u2190\u2192\u2194\u21D0\u21D2\u21D4";
  
  // symboles qui peuvent être en italique s'ils servent d'identifiant
  static final List<List<String>> symboles_id = [
     // grec-minuscule
     ["alpha", "\u03B1"], ["beta", "\u03B2"], ["gamma", "\u03B3"],
     ["delta", "\u03B4"], ["epsilon", "\u03B5"], ["zeta", "\u03B6"],
     ["eta", "\u03B7"], ["theta", "\u03B8"], ["iota", "\u03B9"],
     ["kappa", "\u03BA"], ["lambda", "\u03BB"], ["mu", "\u03BC"],
     ["nu", "\u03BD"], ["xi", "\u03BE"], ["omicron", "\u03BF"],
     ["rho", "\u03C1"], ["sigma", "\u03C3"],
     ["tau", "\u03C4"], ["upsilon", "\u03C5"], ["phi", "\u03C6"],
     ["chi", "\u03C7"], ["psi", "\u03C8"], ["omega", "\u03C9"],
     // grec-majuscule
     ["Alpha", "\u0391"], ["Beta", "\u0392"], ["Gamma", "\u0393"],
     ["Delta", "\u0394"], ["Epsilon", "\u0395"], ["Zeta", "\u0396"],
     ["Eta", "\u0397"], ["Theta", "\u0398"], ["Iota", "\u0399"],
     ["Kappa", "\u039A"], ["Lambda", "\u039B"], ["Mu", "\u039C"],
     ["Nu", "\u039D"], ["Xi", "\u039E"], ["Omicron", "\u039F"],
     ["Pi", "\u03A0"], ["Rho", "\u03A1"], ["Sigma", "\u03A3"],
     ["Tau", "\u03A4"], ["Upsilon", "\u03A5"], ["Phi", "\u03A6"],
     ["Chi", "\u03A7"], ["Psi", "\u03A8"], ["Omega", "\u03A9"],
     // autre grec
     ["thetasym", "\u03D1"], ["upsih", "\u03D2"], ["piv", "\u03D6"],
     ["phiv", "\u03D5"], ["phi1", "\u03D5"]
  ];
  
  // symboles qu'il ne faut pas mettre en italique
  static final List<List<String>> symboles_droits = [
     // grec-minuscule
     ["pi", "\u03C0"], 
     // autres caractères
     ["infin", "\u221E"], ["infty", "\u221E"], ["infini", "\u221E"],
     ["parallel", "\u2225"], ["parallèle", "\u2225"],
     ["sun", "\u2609"], ["soleil", "\u2609"],
     ["star", "\u2605"], ["étoile", "\u2605"],
     ["mercury", "\u263F"], ["mercure", "\u263F"],
     ["venus", "\u2640"], ["vénus", "\u2640"],
     ["earth", "\u2295"], ["terre", "\u2295"], // 2641 est officiel d'après UNICODE mais 2295 est mieux avec STIX...
     ["mars", "\u2642"], ["jupiter", "\u2643"],
     ["saturn", "\u2644"], ["saturne", "\u2644"],
     ["uranus", "\u26E2"], // UNICODE 6.0 draft !
     ["neptun", "\u2646"], ["neptune", "\u2646"],
     ["planck", "\u210F"],
     ["angstrom", "\u212B"], ["angström", "\u212B"],
     ["asterisk", "*"], ["astérisque", "*"], // \uFF0A ?
     ["ell", "\u2113"], ["smalll", "\u2113"], ["petitl", "\u2113"],
     // les noms en Xscr viennent de http://www.w3.org/TR/xml-entity-names/
     ["Ascr", "\u{1D49C}"], ["biga", "\u{1D49C}"], ["granda", "\u{1D49C}"], // \uD835\uDC9C
     ["Bscr", "\u212C"], ["bigb", "\u212C"], ["grandb", "\u212C"],
     ["Cscr", "\u{1D49E}"], ["bigc", "\u{1D49E}"], ["grandc", "\u{1D49E}"], // \uD835\uDC9E
     ["Dscr", "\u{1D49F}"], ["bigd", "\u{1D49F}"], ["grandd", "\u{1D49F}"], // \uD835\uDC9F
     ["Escr", "\u2130"], ["bige", "\u2130"], ["grande", "\u2130"],
     ["Fscr", "\u2131"], ["bigf", "\u2131"], ["grandf", "\u2131"],
     ["Gscr", "\u{1D4A2}"], ["bigg", "\u{1D4A2}"], ["grandg", "\u{1D4A2}"], // \uD835\uDCA2
     ["Hscr", "\u210B"], ["bigh", "\u210B"], ["grandh", "\u210B"],
     ["Iscr", "\u2110"], ["bigi", "\u2110"], ["grandi", "\u2110"],
     ["Jscr", "\u{1D4A5}"], ["bigj", "\u{1D4A5}"], ["grandj", "\u{1D4A5}"], // \uD835\uDCA5
     ["Kscr", "\u{1D4A6}"], ["bigk", "\u{1D4A6}"], ["grandk", "\u{1D4A6}"], // \uD835\uDCA6
     ["Lscr", "\u2112"], ["bigl", "\u2112"], ["grandl", "\u2112"],
     ["Mscr", "\u2133"], ["bigm", "\u2133"], ["grandm", "\u2133"],
     ["Nscr", "\u{1D4A9}"], ["bign", "\u{1D4A9}"], ["grandn", "\u{1D4A9}"], // \uD835\uDCA9
     ["Oscr", "\u{1D4AA}"], ["bigo", "\u{1D4AA}"], ["grando", "\u{1D4AA}"], // \uD835\uDCAA
     ["Pscr", "\u{1D4AB}"], ["bigp", "\u{1D4AB}"], ["grandp", "\u{1D4AB}"], // \uD835\uDCAB
     ["Qscr", "\u{1D4AC}"], ["bigq", "\u{1D4AC}"], ["grandq", "\u{1D4AC}"], // \uD835\uDCAC
     ["Rscr", "\u211B"], ["bigr", "\u211B"], ["grandr", "\u211B"],
     ["Sscr", "\u{1D4AE}"], ["bigs", "\u{1D4AE}"], ["grands", "\u{1D4AE}"], // \uD835\uDCAE
     ["Tscr", "\u{1D4AF}"], ["bigt", "\u{1D4AF}"], ["grandt", "\u{1D4AF}"], // \uD835\uDCAF
     ["Uscr", "\u{1D4B0}"], ["bigu", "\u{1D4B0}"], ["grandu", "\u{1D4B0}"], // \uD835\uDCB0
     ["Vscr", "\u{1D4B1}"], ["bigv", "\u{1D4B1}"], ["grandv", "\u{1D4B1}"], // \uD835\uDCB1
     ["Wscr", "\u{1D4B2}"], ["bigw", "\u{1D4B2}"], ["grandw", "\u{1D4B2}"], // \uD835\uDCB2
     ["Xscr", "\u{1D4B3}"], ["bigx", "\u{1D4B3}"], ["grandx", "\u{1D4B3}"], // \uD835\uDCB3
     ["Yscr", "\u{1D4B4}"], ["bigy", "\u{1D4B4}"], ["grandy", "\u{1D4B4}"], // \uD835\uDCB4
     ["Zscr", "\u{1D4B5}"], ["bigz", "\u{1D4B5}"], ["grandz", "\u{1D4B5}"] // \uD835\uDCB5
  ];
  
  // fonctions qui peuvent se passer de parenthèses quand il n'y a qu'un argument simple
  static final List<String> fctnopar = ["sin", "cos", "tan", "acos", "asin", "atan"];
  
  static final RegExp _numbersExpr = new RegExp("^\\s?([0-9]+([\\.,][0-9]+)?|[\\.,][0-9]+)([Ee][+-]?[0-9]+)?\\s?\$");
  
  
  StringMathBuilder(final String s) {
    _rootElement = new MathRootElement();
    final String s2 = ajParentheses(replaceSpecial(s));
    if (s != '') {
      final JEQ jeq = parser(s2);
      MathElement me;
      if (jeq == null)
        me = null;
      else
        me = jeq.versMathML();
      _rootElement.addMathElement(me);
    }
  }
  
  /**
   * Return the root  element of a math tree
  *
   * @return Root element
   */
  MathRootElement getMathRootElement()
  {
    return _rootElement;
  }
  
  String replaceSpecial(String s) {
    for (final List<String> spec in special) {
      int ind = s.indexOf(spec[0]);
      while (ind != -1) {
        s = s.substring(0, ind) + spec[1] + s.substring(ind + spec[0].length);
        ind = s.indexOf(spec[0]);
      }
    }
    return s;
  }
  
  // renvoie true si le caractère avec la position donnée dans le String est bien un opérateur
  // (en résolvant la difficulté d'expressions comme "1e-1/2+e-1/2")
  static bool operateurEn(String s, int pos) {
    String c = s[pos];
    if (sops.indexOf(c) == -1)
      return(false);
    if (c != '+' && c != '-')
      return(true);
    if (pos < 2)
      return(true);
    c = s[pos - 1];
    if (c != 'E' && c != 'e')
      return(true);
    c = s[pos - 2];
    if ("0123456789".indexOf(c) != -1)
      return(false);
    return(true);
  }
  
  static String ajParentheses(String s) {
    // d'abord ajouter des parenthèses pour séparer les éléments des fonctions
    // f(a+1;b;c) -> f((a+1);b;c)
    int indop = s.indexOf(';');
    while (indop != -1) {
      // vers la gauche du ;
      int pp = 0;
      bool yaop = false;
      String c;
      for (int i=indop-1; i>=0 && pp>=0; i--) {
        c = s[i];
        if (c == ';' && pp == 0)
          break; // les parenthèses sont déjà ajoutées
        if (c == '(')
          pp--;
        else if (c == ')')
          pp++;
        else if (operateurEn(s, i))
          yaop = true;
        if (pp < 0 && yaop) {
          s = s.substring(0,i) + '(' + s.substring(i,indop) + ')' + s.substring(indop);
          indop += 2;
        }
      }
      // vers la droite du ;
      pp = 0;
      yaop = false;
      for (int i=indop+1; i<s.length && pp>=0; i++) {
        c = s[i];
        if (c == '(')
          pp++;
        else if (c == ')')
          pp--;
        else if (operateurEn(s, i))
          yaop = true;
        if ((pp < 0 || pp == 0 && c == ';') && yaop)
          s = s.substring(0,indop+1) + '(' + s.substring(indop+1,i) + ')' + s.substring(i);
        if (c == ';' && pp == 0)
          break;
      }
      final int indop2 = s.substring(indop+1).indexOf(';');
      if (indop2 == -1)
        indop = indop2;
      else
        indop += indop2 + 1;
    }
    
    // les autres parenthèses
    for (int iops=0; iops<sops.length; iops++) {
      final String cops = sops[iops];
      indop = s.indexOf(cops);
      while (indop != -1 && !operateurEn(s, indop))
        indop = s.indexOf(cops, indop + 1);
      int nindop = indop;
      int im,ip;
      String cm=' ',cp=' ';
      int pp;
      bool ajp;
      while (nindop != -1) {
        ajp = false;
        im = indop - 1;
        if (im >= 0)
          cm = s[im];
        pp = 0;
        while (im >= 0 && (pp != 0 || cm != '(') &&
            (pp != 0 || !operateurEn(s, im))) {
          if (cm == ')')
            pp++;
          else if (cm == '(')
            pp--;
          im--;
          if (im >= 0)
            cm = s[im];
        }
        if (im < 0 || operateurEn(s, im))
          ajp = true;
        ip = indop + 1;
        if (ip >= 0 && ip <= s.length-1)
          cp = s[ip];
        pp = 0;
        while (ip < s.length && (pp != 0 || cp != ')') &&
        (pp != 0 || !operateurEn(s, ip))) {
          if (cp == '(')
            pp++;
          else if (cp == ')')
            pp--;
          ip++;
          if (ip < s.length)
            cp = s[ip];
        }
        if (ip >= s.length || operateurEn(s, ip))
          ajp = true;
        if (ajp) {
          s = s.substring(0, im+1) + "(" + s.substring(im+1, ip) + ")" +
              s.substring(ip);
          indop++;
        }
        nindop = s.substring(indop+1).indexOf(cops);
        indop = nindop + indop+1;
      }
    }
    return s;
  }
  
  JEQ parser(String s) {
    if (s == null || s == '')
      return null;
    
    if (s[0] == '(' && s[s.length-1] == ')') {
      int pp = 0;
      for (int i=1; i<s.length-1; i++) {
        if (s[i] == '(')
          pp++;
        else if (s[i] == ')')
          pp--;
        if (pp == -1)
          break;
      }
      if (pp != -1)
        s = s.substring(1, s.length-1);
    }
    
    int indop = -1;
    int pp = 0;
    for (int i=0; i<s.length; i++) {
      if (pp == 0 && operateurEn(s, i)) {
        indop = i;
        break;
      } else if (s[i] == '(')
          pp++;
      else if (s[i] == ')')
        pp--;
    }
    if (indop == -1) {
      bool nb;
      try {
        nb = _numbersExpr.hasMatch(s);
      } on FormatException catch(ex) {
        nb = false;
      }
      if (nb)
        return(new JEQNombre(s));
      final int indf = s.indexOf('(');
      if (indf != -1 && s[s.length-1] == ')') {
        // nomfct(p1; p2; ...) ou (nomfctcomplexe)(p1; p2; ...) ?
        // comme (sin^2)(alpha) ou (theta_f)(1)
        // recherche d'une deuxième parenthèse au même niveau que la première
        int indf2 = -1;
        pp = 0;
        for (int i=0; i<s.length; i++) {
          final String c = s[i];
          if (c == '(' && pp == 0 && i != indf) {
            indf2 = i;
            break;
          } else if (c == '(')
            pp++;
          else if (c == ')')
            pp--;
        }
        String nomfct = null;
        JEQ nom = null;
        if (indf2 == -1) {
          nom = new JEQVariable(s.substring(0,indf));
          s = s.substring(indf+1, s.length-1);
        } else {
          nom = parser(s.substring(0, indf2));
          s = s.substring(indf2+1, s.length-1);
        }
        // recherche des paramètres
        final List<JEQ> vp = new List<JEQ>();
        //indv = s.indexOf(';'); marche pas avec f(g(a;b);c)
        int indv = -1;
        pp = 0;
        for (int i=0; i<s.length; i++) {
          final String c = s[i];
          if (c == ';' && pp == 0 ) {
            indv = i;
            break;
          } else if (c == '(')
            pp++;
          else if (c == ')')
            pp--;
        }
        if (indv == -1)
          vp.add(parser(s.trim()));
        else
          while (indv != -1) {
            vp.add(parser(s.substring(0,indv).trim()));
            s = s.substring(indv+1);
            indv = -1;
            pp = 0;
            for (int i=0; i<s.length; i++) {
              final String c = s[i];
              if (c == ';' && pp == 0 ) {
                indv = i;
                break;
              } else if (c == '(')
                pp++;
              else if (c == ')')
                pp--;
            }
            if (indv == -1)
              vp.add(parser(s.trim()));
          }
        return(new JEQFonction(nom, vp));
      } else
        return(new JEQVariable(s));
    }
    
    final String op = s[indop];
    final String s1 = s.substring(0,indop).trim();
    JEQ p1;
    if (s1 == '')
      p1 = null;
    else
      p1 = parser(s1);
    final String s2 = s.substring(indop+1).trim();
    JEQ p2;
    if (s2 == '')
      p2 = null;
    else
      p2 = parser(s2);
    
    return(new JEQOperation(op, p1, p2));
  }
  
  static MathElement elemOrQuestion(final JEQ jeq) {
    if (jeq != null)
      return jeq.versMathML();
    final MathText mtext = new MathText();
    mtext.addText("?");
    return mtext;
  }
  
  
}

abstract class JEQ {
  MathElement versMathML();
  void setUnites();
}

class JEQFonction implements JEQ {
  JEQ nom;
  List<JEQ> vp;
  final RegExp namesExpr = new RegExp("^[0-9]+.*\$");
  JEQFonction(final JEQ nom, final List<JEQ> arguments) {
    this.nom = nom;
    if (nom is JEQVariable && namesExpr.hasMatch(nom.nom)) {
      // un nom de fonction qui commence par des chiffres ?!?
      nom.nom = "?";
    }
    vp = arguments;
    if ((getNomFct() == "unité" || getNomFct() == "unit") && vp.length > 1 && vp[1] != null)
      vp[1].setUnites();
  }
  void setUnites() {
    for (JEQ param in vp)
      if (param != null)
        param.setUnites();
  }
  String getNomFct() {
    if (nom is JEQVariable)
      return((nom as JEQVariable).nom);
    else
      return(null);
  }
  MathElement versMathML() {
    JEQ p1, p2, p3, p4;
    if (vp.length > 0)
      p1 = vp[0];
    else
      p1 = null;
    if (vp.length > 1)
      p2 = vp[1];
    else
      p2 = null;
    if (vp.length > 2)
      p3 = vp[2];
    else
      p3 = null;
    if (vp.length > 3)
      p4 = vp[3];
    else
      p4 = null;
    String nomfct = getNomFct();
    MathElement me;
    if (nomfct == "sqrt" || (nomfct == "racine" && (p1 == null || p2 == null))) {
      me = new MathSqrt();
      me.addMathElement(StringMathBuilder.elemOrQuestion(p1));
    } else if (nomfct == "racine" || nomfct == "root") {
      me = new MathRoot();
      me.addMathElement(StringMathBuilder.elemOrQuestion(p1));
      me.addMathElement(StringMathBuilder.elemOrQuestion(p2));
    } else if (nomfct == "exp") {
      me = new MathSup();
      final MathIdentifier mi = new MathIdentifier();
      mi.addText("e");
      me.addMathElement(mi);
      me.addMathElement(StringMathBuilder.elemOrQuestion(p1));
    } else if (nomfct == "abs") {
      me = new MathRow();
      MathOperator mo = new MathOperator();
      mo.addText("|");
      me.addMathElement(mo);
      me.addMathElement(StringMathBuilder.elemOrQuestion(p1));
      mo = new MathOperator();
      mo.addText("|");
      me.addMathElement(mo);
    } else if (nomfct == "norm" || nomfct == "norme") {
      me = new MathRow();
      MathOperator mo = new MathOperator();
      mo.addText("\u2016");
      me.addMathElement(mo);
      me.addMathElement(StringMathBuilder.elemOrQuestion(p1));
      mo = new MathOperator();
      mo.addText("\u2016");
      me.addMathElement(mo);
    } else if (nomfct == "fact" || nomfct == "factorielle"
        || nomfct == "factorial") {
      me = new MathRow();
      MathOperator mo;
      if (!(p1 is JEQVariable || p1 is JEQNombre)) {
        mo = new MathOperator();
        mo.addText("(");
        me.addMathElement(mo);
      }
      me.addMathElement(p1.versMathML());
      if (!(p1 is JEQVariable || p1 is JEQNombre)) {
        mo = new MathOperator();
        mo.addText(")");
        me.addMathElement(mo);
      }
      mo = new MathOperator();
      mo.addText("!");
      me.addMathElement(mo);
    } else if (nomfct == "int" || nomfct == "intégrale") {
      me = new MathRow();
      MathOperator mo = new MathOperator();
      mo.addText("\u222B");
      mo.setStretchy(true);
      if (p3 != null && p4 != null) {
        final MathUnderOver munderover = new MathUnderOver();
        munderover.addMathElement(mo);
        munderover.addMathElement(StringMathBuilder.elemOrQuestion(p3));
        munderover.addMathElement(StringMathBuilder.elemOrQuestion(p4));
        me.addMathElement(munderover);
      } else if (p3 != null) {
        final MathUnder munder = new MathUnder();
        munder.addMathElement(mo);
        munder.addMathElement(StringMathBuilder.elemOrQuestion(p3));
        me.addMathElement(munder);
      } else if (p4 != null) {
        final MathOver mover = new MathOver();
        mover.addMathElement(mo);
        mover.addMathElement(StringMathBuilder.elemOrQuestion(p4));
        me.addMathElement(mover);
      } else
        me.addMathElement(mo);
      final MathRow mrow = new MathRow();
      mrow.addMathElement(StringMathBuilder.elemOrQuestion(p1));
      // <mo> ⁢ </mo>
      if (p2 != null) {
        mo = new MathOperator();
        mo.addText("d"); // ⅆ
        mrow.addMathElement(mo);
        mrow.addMathElement(p2.versMathML());
      }
      me.addMathElement(mrow);
    } else if (nomfct == "prod" || nomfct == "sum" ||
        nomfct == "produit" || nomfct == "somme") {
      me = new MathRow();
      final MathUnderOver munderover = new MathUnderOver();
      final MathOperator mo = new MathOperator();
      if (nomfct == "prod" || nomfct == "produit")
        mo.addText("\u220F");
      else if (nomfct == "sum" || nomfct == "somme")
        mo.addText("\u2211");
      mo.setStretchy(true);
      munderover.addMathElement(mo);
      munderover.addMathElement(StringMathBuilder.elemOrQuestion(p2));
      munderover.addMathElement(StringMathBuilder.elemOrQuestion(p3));
      me.addMathElement(munderover);
      final MathRow mrow = new MathRow();
      mrow.addMathElement(StringMathBuilder.elemOrQuestion(p1));
      me.addMathElement(mrow);
    } else if (nomfct == "over" || nomfct == "dessus") {
      final MathOver mover = new MathOver();
      mover.addMathElement(StringMathBuilder.elemOrQuestion(p1));
      mover.addMathElement(StringMathBuilder.elemOrQuestion(p2));
      me = mover;
    } else if (nomfct == "subsup") {
      final MathSubSup msubsup = new MathSubSup();
      msubsup.addMathElement(StringMathBuilder.elemOrQuestion(p1));
      msubsup.addMathElement(StringMathBuilder.elemOrQuestion(p2));
      msubsup.addMathElement(StringMathBuilder.elemOrQuestion(p3));
      me = msubsup;
    } else if (nomfct == "accent") {
      final MathOver mover = new MathOver();
      mover.setAccent(true);
      mover.addMathElement(StringMathBuilder.elemOrQuestion(p1));
      if (p2 is JEQOperation && (p2 as JEQOperation).op == '\u223C') {
        final MathOperator mo = new MathOperator();
        mo.setStretchy(true);
        mo.addText("\u223C");
        mover.addMathElement(mo);
      } else
        mover.addMathElement(StringMathBuilder.elemOrQuestion(p2));
      me = mover;
    } else if (nomfct == "matrix" || nomfct == "matrice") {
      me = new MathRow();
      MathOperator mo = new MathOperator();
      mo.addText("(");
      me.addMathElement(mo);
      final MathTable mtable = new MathTable();
      for (final JEQ mel in vp)
        mtable.addMathElement(StringMathBuilder.elemOrQuestion(mel));
          me.addMathElement(mtable);
          mo = new MathOperator();
          mo.addText(")");
          me.addMathElement(mo);
    } else if (nomfct == "system" || nomfct == "système") {
      me = new MathRow();
      final MathOperator mo = new MathOperator();
      mo.addText("{");
      me.addMathElement(mo);
      final MathTable mtable = new MathTable();
      for (final JEQ mel in vp) {
        final MathRow mrow = new MathRow();
        final MathTableData mtd = new MathTableData();
        mtd.setColumnAlign("left");
        mtd.addMathElement(StringMathBuilder.elemOrQuestion(mel));
        mrow.addMathElement(mtd);
        mtable.addMathElement(mrow);
      }
      me.addMathElement(mtable);
    } else if (nomfct == "line" || nomfct == "ligne") {
      me = new MathTableRow();
      for (final JEQ mel in vp) {
        final MathTableData mtd = new MathTableData();
        mtd.addMathElement(StringMathBuilder.elemOrQuestion(mel));
        me.addMathElement(mtd);
      }
    } else if (nomfct == "slash") {
      me = new MathRow();
      me.addMathElement(StringMathBuilder.elemOrQuestion(p1));
      final MathOperator mo = new MathOperator();
      mo.addText("/"); // could be \u2215
      me.addMathElement(mo);
      me.addMathElement(StringMathBuilder.elemOrQuestion(p2));
    } else if (nomfct == "frac" || nomfct == "fraction") {
      final MathFrac mfrac = new MathFrac();
      mfrac.addMathElement(StringMathBuilder.elemOrQuestion(p1));
      mfrac.addMathElement(StringMathBuilder.elemOrQuestion(p2));
      me = mfrac;
    } else if (nomfct == "pscalaire" || nomfct == "scalarp") {
      final MathRow mrow = new MathRow();
      mrow.addMathElement(StringMathBuilder.elemOrQuestion(p1));
      final MathOperator mo = new MathOperator();
      mo.addText(".");
      mrow.addMathElement(mo);
      mrow.addMathElement(StringMathBuilder.elemOrQuestion(p2));
      return mrow;
    } else if (nomfct == "dtemps" || nomfct == "timed") {
      final MathOver mover = new MathOver();
      mover.setAccent(true);
      mover.addMathElement(StringMathBuilder.elemOrQuestion(p1));
      MathOperator mop = new MathOperator();
      if (p2 is JEQNombre) {
        try {
          final int n = int.parse((p2 as JEQNombre).valeur);
          String spts = "";
          for (int i=0; i<n; i++)
            spts = spts + '.';
          mop.addText(spts);
        } on FormatException catch(ex) {
          mop.addText("?");
        }
      } else
        mop.addText("?");
      mover.addMathElement(mop);
      me = mover;
    } else if (nomfct == "unité" || nomfct == "unit") {
      final MathRow mrow = new MathRow();
      mrow.addMathElement(StringMathBuilder.elemOrQuestion(p1));
      final MathOperator mo = new MathOperator();
      mo.addText(" ");
      mrow.addMathElement(mo);
      mrow.addMathElement(StringMathBuilder.elemOrQuestion(p2));
      return mrow;
    } else if (nomfct == "moyenne" || nomfct == "mean") {
      me = new MathRow();
      MathOperator mo = new MathOperator();
      mo.addText("\u2329");
      me.addMathElement(mo);
      me.addMathElement(StringMathBuilder.elemOrQuestion(p1));
      mo = new MathOperator();
      mo.addText("\u232A");
      me.addMathElement(mo);
    } else if (nomfct == "vecteur" || nomfct == "vector") {
      // les vecteurs sont affichés en gras quand c'est possible
      final MathElement mp1 = StringMathBuilder.elemOrQuestion(p1);
      if (mp1 is MathIdentifier) {
        mp1.setMathvariant("bold");
        me = mp1;
      } else if (mp1 is MathSub && mp1.getMathElement(0) is MathIdentifier) {
        (mp1.getMathElement(0) as MathIdentifier).setMathvariant("bold");
        me = mp1;
      } else {
        final MathOver mover = new MathOver();
        mover.setAccent(true);
        mover.addMathElement(StringMathBuilder.elemOrQuestion(p1));
        final MathOperator mo = new MathOperator();
        mo.setStretchy(true);
        mo.addText("\u2192");
        mover.addMathElement(mo);
        me = mover;
      }
    } else {
      me = new MathRow();
      bool par = true;
      if (nom is JEQVariable) {
        MathText mt = new MathText();
        for (final List<String> tsymbole in StringMathBuilder.symboles_id)
          if (nomfct == tsymbole[0]) {
            nomfct = tsymbole[1];
            break;
          }
        for (final List<String> tsymbole in StringMathBuilder.symboles_droits)
          if (nomfct == tsymbole[0]) {
            nomfct = tsymbole[1];
            break;
          }
        mt.addText(nomfct);
        if (p2 == null && p1 is JEQVariable)
          for (final String element in StringMathBuilder.fctnopar)
            if (nomfct == element) {
              par = false;
              break;
            }
        me.addMathElement(mt);
      } else
        me.addMathElement(nom.versMathML());
      if (par) {
        final MathOperator mo = new MathOperator();
        mo.addText("("); // \u2061 = ⁡ serait mieux mais pas reconnu
        me.addMathElement(mo);
      }
      me.addMathElement(StringMathBuilder.elemOrQuestion(p1));
      for (int i=1; i<vp.length; i++) { // ATTENTION, LA BOUCLE COMMENCE A 1
        final MathOperator mo = new MathOperator();
      mo.addText(";");
      me.addMathElement(mo);
      me.addMathElement(vp[i].versMathML());
      }
      if (par) {
        final MathOperator mo = new MathOperator();
        mo.addText(")");
        me.addMathElement(mo);
      } else {
        final MathText mtext = new MathText();
        mtext.addText("\u00A0"); // nbsp, mspace serait mieux
        me.addMathElement(mtext);
      }
    }
    return me;
  }
}

class JEQOperation implements JEQ {
  String op;
  JEQ p1, p2;
  bool unites;
  JEQOperation(final String operateur, final JEQ p1, final JEQ p2) {
    op = operateur;
    this.p1 = p1;
    this.p2 = p2;
    unites = false;
    if (op == '#' && p2 != null)
      p2.setUnites();
  }
  void setUnites() {
    unites = true;
    if (p1 != null)
      p1.setUnites();
    if (p2 != null)
      p2.setUnites();
  }
  MathElement versMathML() {
    if (op == '/') {
      final MathFrac mfrac = new MathFrac();
      mfrac.addMathElement(StringMathBuilder.elemOrQuestion(p1));
      mfrac.addMathElement(StringMathBuilder.elemOrQuestion(p2));
      return mfrac;
    } else if (op == '^') {
      final MathSup msup = new MathSup();
      bool par;
      if (p1 is JEQFonction) {
        String nomfct = (p1 as JEQFonction).getNomFct();
        if (nomfct == "sqrt" || nomfct == "racine")
          par = false;
        else if (nomfct == "abs")
          par = false;
        else if (nomfct == "matrice")
          par = false;
        else if (nomfct == "dtemps" || nomfct == "timed")
          par = false;
        else
          par = true;
      } else if (p1 is JEQOperation) {
        String op = (p1 as JEQOperation).op;
        if (op == '_')
          par = false;
        else
          par = true;
        
      } else
        par = false;
      MathElement me1;
      if (par)
        me1 = ajPar(p1.versMathML());
      else
        me1 = StringMathBuilder.elemOrQuestion(p1);
      msup.addMathElement(me1);
      msup.addMathElement(StringMathBuilder.elemOrQuestion(p2));
      return msup;
    } else if (op == '_') {
      final MathSub msub = new MathSub();
      msub.addMathElement(StringMathBuilder.elemOrQuestion(p1));
      msub.addMathElement(StringMathBuilder.elemOrQuestion(p2));
      return msub;
    } else if (op == '#') { // équivalent à la fonction unité
      final MathRow mrow = new MathRow();
      mrow.addMathElement(StringMathBuilder.elemOrQuestion(p1));
      mrow.addMathElement(StringMathBuilder.elemOrQuestion(p2));
      return mrow;
    } else if (op == '*') {
      final MathRow mrow = new MathRow();
      MathElement me1 = StringMathBuilder.elemOrQuestion(p1);
      if (p1 is JEQOperation && "+-".indexOf((p1 as JEQOperation).op) != -1)
        me1 = ajPar(me1);
      mrow.addMathElement(me1);
      
      /*
      JEQ dernierDansP1 = p1;
      if (p1 != null)
      while (dernierDansP1 is JEQOperation && ((JEQOperation)dernierDansP1).p2 != null) {
      dernierDansP1 = ((JEQOperation)dernierDansP1).p2;
      }
      final bool p1nombre = dernierDansP1 is JEQNombre;
       */
      JEQ premierDansP2 = p2;
      if (p2 != null)
        while (premierDansP2 is JEQOperation && (premierDansP2 as JEQOperation).p1 != null) {
          premierDansP2 = (premierDansP2 as JEQOperation).p1;
        }
      final bool p2nombre = premierDansP2 is JEQNombre;
      bool pscalaire1 = false;
      if (p1 is JEQFonction) {
        final String nomfct = (p1 as JEQFonction).getNomFct();
        if (nomfct == "pscalaire" || nomfct == "scalarp")
          pscalaire1 = true;
      }
      bool pscalaire2 = false;
      if (p2 is JEQFonction) {
        final String nomfct = (p2 as JEQFonction).getNomFct();
        if (nomfct == "pscalaire" || nomfct == "scalarp")
          pscalaire2 = true;
      }
      if ((/*p1nombre && */p2nombre) || (pscalaire1 && pscalaire2)) {
        final MathOperator mo = new MathOperator();
        mo.addText("×");
        mrow.addMathElement(mo);
      } else {
        if (unites) {
          final MathOperator mo = new MathOperator();
          mo.addText(".");
          mrow.addMathElement(mo);
        }
        // else <mo> ⁢ </mo>
      }
      
      MathElement me2 = StringMathBuilder.elemOrQuestion(p2);
      if (p2 is JEQOperation && "+-".indexOf((p2 as JEQOperation).op) != -1)
        me2 = ajPar(me2);
      mrow.addMathElement(me2);
      return mrow;
    } else if (op == '-') {
      final MathRow mrow = new MathRow();
      if (p1 != null)
        mrow.addMathElement(p1.versMathML());
      final MathOperator mo = new MathOperator();
      mo.addText("-");
      mrow.addMathElement(mo);
      if (p2 != null) {
        MathElement me2 = p2.versMathML();
        if (p2 is JEQOperation && "+-".indexOf((p2 as JEQOperation).op) != -1)
          me2 = ajPar(me2);
        mrow.addMathElement(me2);
      }
      return mrow;
    } else if (op == '+') {
      final MathRow mrow = new MathRow();
      if (p1 != null)
        mrow.addMathElement(p1.versMathML());
      final MathOperator mo = new MathOperator();
      mo.addText("+");
      mrow.addMathElement(mo);
      if (p2 != null) {
        MathElement me2 = p2.versMathML();
        if (me2 is MathRow && me2.getMathElementCount() > 0) {
          MathElement me2b = me2;
          while (me2b is MathRow && me2b.getMathElementCount() > 0)
            me2b = me2b.getMathElement(0);
          if (me2b.getText() == "-")
            me2 = ajPar(me2);
        }
        mrow.addMathElement(me2);
      }
      return mrow;
    } else {
      final MathRow mrow = new MathRow();
      if (p1 != null) {
        MathElement me1 = p1.versMathML();
        if (op == '\u2227' && p1 is JEQOperation && "+-".indexOf((p1 as JEQOperation).op) != -1)
          me1 = ajPar(me1);
        mrow.addMathElement(me1);
      }
      final MathOperator mo = new MathOperator();
      mo.addText(op);
      if ("=\u2260\u2248\u223C\u2261\u2264\u2265\u226A\u226B\u221D".indexOf(op) != -1) {
        // espace autour des opérateurs d'égalité
        mo.setLspace(0.5);
        mo.setRspace(0.5);
      }
      mrow.addMathElement(mo);
      if (p2 != null) {
        MathElement me2 = p2.versMathML();
        if (op == '\u2227' && p2 is JEQOperation && "+-".indexOf((p2 as JEQOperation).op) != -1)
          me2 = ajPar(me2);
        mrow.addMathElement(me2);
      }
      return mrow;
    }
  }
  MathElement ajPar(final MathElement me) {
    final MathRow mrow = new MathRow();
    MathOperator mo = new MathOperator();
    mo.addText("(");
    mrow.addMathElement(mo);
    mrow.addMathElement(me);
    mo = new MathOperator();
    mo.addText(")");
    mrow.addMathElement(mo);
    return mrow;
  }
}

class JEQNombre implements JEQ {
  String valeur;
  JEQNombre(final String valeur) {
    this.valeur = valeur;
  }
  void setUnites() {
  }
  MathElement versMathML() {
    final MathNumber mn = new MathNumber();
    mn.addText(valeur);
    return mn;
  }
}

class JEQVariable implements JEQ {
  String nom;
  bool unites;
  final RegExp badExpr = new RegExp("^[0-9]+[a-zA-Z]+\$");
  JEQVariable(final String nom) {
    this.nom = nom.trim();
    unites = false;
  }
  void setUnites() {
    unites = true;
  }
  MathElement versMathML() {
    if (nom == "hat" || nom == "chapeau") {
      final MathOperator mo = new MathOperator();
      mo.setStretchy(true);
      mo.addText("^");
      return mo;
    } else if (nom == "bar" || nom == "barre") {
      final MathOperator mo = new MathOperator();
      mo.setStretchy(true);
      mo.addText("\u00AF");
      return mo;
    } else {
      String s = nom;
      bool droit = unites;  // les unités ne doivent pas être en italique
      for (final List<String> tsymbole in StringMathBuilder.symboles_id)
        if (s == tsymbole[0]) {
          s = tsymbole[1];
          break;
        }
      for (final List<String> tsymbole in StringMathBuilder.symboles_droits)
        if (s == tsymbole[0]) {
          s = tsymbole[1];
          droit = true;
          break;
        }
      if (s.indexOf(',') != -1 || s.indexOf('.') != -1 || s.indexOf('(') != -1 || s.indexOf(')') != -1)
        s = "?"; // les ',' les '.' et les parenthèses sont interdits dans les noms de variables
      if (s.indexOf(' ') != -1)
        s = s.replaceAll(" ", "?"); // les espaces aussi !
      if (s.indexOf('\u00A0') != -1) // pour les petits malins
        s = s.replaceAll("\u00A0", "?");
      if (badExpr.hasMatch(s))
        s = "?"; // il manque l'opérateur *
      MathElement me;
      if (droit)
        me = new MathText();
      else
        me = new MathIdentifier();
      me.addText(s);
      return me;
    }
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/equations/text_metrics.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/equations/text_metrics.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of equations;

// Someday we will use canvas' TextMetrics instead of this class
// http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#drawing-text-to-the-bitmap

class TextMetrics {
  static final canvas = new h.CanvasElement(width:500, height:300);
  double width;
  double height;
  double ascent;
  double descent;
  double actualBoundingBoxAscent;
  double actualBoundingBoxDescent;
  
  TextMetrics(final String s, final String font) {
    h.SpanElement text = new h.SpanElement();
    text.setAttribute('style', "font: $font");
    text.text = s;
    h.DivElement block = new h.DivElement();
    block.setAttribute('style', 'display: inline-block; width: 1px; height: 0px;');
    
    h.DivElement div = new h.DivElement();
    div.append(text);
    div.append(block);
    
    h.document.body.append(div);
    
    width = text.offset.width.toDouble();
    
    block.style.verticalAlign = 'bottom';
    height = (block.offset.top - text.offset.top).toDouble();
    
    block.style.verticalAlign = 'baseline';
    ascent = (block.offset.top - text.offset.top).toDouble();
    
    descent = height - ascent;
    
    div.remove();
    
    // to get the real ascent/descent, we have to use a canvas :(
    h.CanvasRenderingContext2D context = canvas.context2D;
    context.font = font;
    context.fillStyle = 'white';
    context.fillRect(0, 0, canvas.width, canvas.height);
    context.fillStyle = 'black';
    context.fillText(s, 0, ascent);
    int cw = width.ceil();
    int ch = height.ceil();
    List<int> data = context.getImageData(0, 0, cw, ch).data;
    int top1 = 0;
    bool t1 = false;
    int top2 = top1;
    int bottom1 = ch - 1;
    bool b1 = false;
    int bottom2 = bottom1;
    for (int i=0; i < cw * ch; i++) {
      if (!t1 && data[i*4] != 0xFF) {
        top1 = i ~/ cw;
        top2 = top1;
        t1 = true;
      }
      if (data[i*4] < 0x90) {
        top2 = i ~/ cw;
        break;
      }
    }
    double top = (top1 + top2) / 2;
    for (int i=cw * ch - 1; i >= 0; i--) {
      if (!b1 && data[i*4] != 0xFF) {
        bottom1 = i ~/ cw;
        bottom2 = bottom1;
        b1 = true;
      }
      if (data[i*4] < 0x90) {
        bottom2 = i ~/ cw;
        break;
      }
    }
    double bottom = (bottom1 + bottom2) / 2;
    actualBoundingBoxAscent = ascent - top;
    actualBoundingBoxDescent = bottom - ascent;
  }
  
}


Index: modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_element.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_element.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of equations;

/**
 * The basic class the the math elements. From this class elements inherits.
 */
class MathElement {

  /** The URI from MathML */
  static final String URI = "http://www.w3.org/1998/Math/MathML";

  MathBase base;
  MathElement parent;
  int fontsize = 14;
  final List<MathElement> children = new List<MathElement>();

  final StringBuffer text = new StringBuffer();

  /**
   * Creates a math element
   *
   * @param base The base for the math element tree
   * @param fontsize The font size for this element
   */
  MathElement([final MathBase base, final int fontsize]) {
    if (base != null)
      setMathBase(base);
    if (fontsize != null)
      setFontSize(fontsize);
  }
  
  /**
   * Add a math element as a child
   *
   * @param child Math element
   */
  void addMathElement(final MathElement child) {
    if (child != null) {
      children.add(child);
      child.setMathBase(base);
      child.setParent(this);
      child.setFontSize(fontsize);
    }
  }

  /**
   * Gets a child from this element
   *
   * @param index Index of the child
   *
   * @return The child
   */
  MathElement getMathElement(final int index) {
    if ((index >= 0) && (index < children.length))
      return children[index];
    return null;
  }

  /**
   * Sets a child from this element
   *
   * @param index Index of the child
   *
   * @return The child
   */
  void setMathElementAt(final MathElement child, final int index) {
    if ((index >= 0) && (index < children.length))
      children[index] = child;
  }

  /**
   * Returns the count of children from this element
   *
   * @return Count of children
   */
  int getMathElementCount() {
    return children.length;
  }

  /**
   * Add the content of a String to this element
   *
   * @param text Text
   */
  void addText(final String text) {
    for (int i = 0; i < text.length; i++)
      if (" \t\n\r".indexOf(text[i]) < 0)
        this.text.write(text[i]);
      else if ((' ' == text[i]) && (i>0) && (' ' != text[i - 1]))
        this.text.write(text[i]);
  }

  /**
   * Returns the text contentof this element
   *
   * @return Text content
   */
  String getText() {
    return text.toString().trim();
  }

  /**
   * Sets the base for this element
   *
   * @param base Math base
   */
  void setMathBase(final MathBase base) {
    this.base = base;
    for (final MathElement e in children)
      e.setMathBase(base);
  }

  /**
   * Gets the math base
   *
   * @return Math base
   */
  MathBase getMathBase() {
    return base;
  }

  /**
   * Sets the parent of this element
   *
   * @param parent Parent element
   */
  void setParent(final MathElement parent) {
    this.parent = parent;
  }

  /**
   * Returns get parent of this element
   *
   * @return Parent element
   */
  MathElement getParent() {
    return parent;
  }

  /**
   * Sets the font size for this component
   *
   * @param fontsize Font size
   */
  void setFontSize(final int fontsize) {
    this.fontsize = max(fontsize, 8);
    for (final MathElement e in children)
      e.setFontSize(this.fontsize);
  }

  /**
   * Gets the used font size
   *
   * @return Font Size
   */
  int getFontSize() {
    return fontsize;
  }

  /**
   * Gets the used font
   *
   * @return Font
   */
  String getFont() {
    if (base != null)
      return base.getFont(fontsize);
    return null;
  }

  String getItalicFont() {
    if (base != null)
      return base.getFont(fontsize, MathBase.STYLE_ITALIC);
    return null;
  }

  String getBoldFont() {
    if (base != null)
      return base.getFont(fontsize, MathBase.STYLE_BOLD);
    return null;
  }

  String getBoldItalicFont() {
    if (base != null)
      return base.getFont(fontsize, MathBase.STYLE_BOLD_ITALIC);
    return null;
  }

  /**
   * Gets the font metrics of the used font
   *
   * @return Font metrics
   */
  TextMetrics getFontMetrics() {
    if (base != null)
      return base.getFontMetrics(fontsize);
    return null;
  }
  
  double stringWidth(s) {
    return(base.stringWidth(s, getFont()));
  }
  
  TextMetrics getStringMetrics(String s) {
    return(new TextMetrics(s, getFont()));
  }
  
  /**
   * Paints a border around this element as debug information
   *
   * @param context The graphics context to use for painting
   * @param posX The first left position for painting
   * @param posY The position of the baseline
   */
  void debug(final h.CanvasRenderingContext2D context, final double posX, final double posY) {
    context.strokeStyle = 'blue';
    context.beginPath();
    context.moveTo(posX, posY - getAscentHeight(true));
    context.lineTo(posX + getWidth(true), posY - getAscentHeight(true));
    context.moveTo(posX + getWidth(true), posY - getAscentHeight(true));
    context.lineTo(posX + getWidth(true), posY + getDescentHeight(true));
    context.moveTo(posX, posY + getDescentHeight(true));
    context.lineTo(posX + getWidth(true), posY + getDescentHeight(true));
    context.moveTo(posX, posY - getAscentHeight(true));
    context.lineTo(posX, posY + getDescentHeight(true));
    context.stroke();
    context.strokeStyle = 'red';
    context.beginPath();
    context.moveTo(posX, posY);
    context.lineTo(posX + getWidth(true), posY);
    context.stroke();
    context.strokeStyle ='black';
  }

  /**
   * Paints this element
   *
   * @param context The graphics context to use for painting
   * @param posX The first left position for painting
   * @param posY The position of the baseline
   */
  void paint(final h.CanvasRenderingContext2D context, final double posX, final double posY) {
    if (base.isDebug())
      debug(context, posX, posY);

    double pos = posX;
    MathElement child;

    for (int i = 0; i < getMathElementCount(); i++)
    {
      child = getMathElement(i);
      child.paint(context, pos, posY);
      pos += child.getWidth(true) + 2;
    }
  }

  /**
   * Return the current width of this element
   *
   * @param dynamicParts
   *
   * @return Width of this element
   */
  double getWidth(final bool dynamicParts) {
    double width = 0.0;
    
    for (final MathElement e in children)
      width += e.getWidth(dynamicParts) + 2;
    return width - 2;
  }

  /**
   * Return the current height of this element
   *
   * @param dynamicParts Should be true, if the calculation consider the elements,
   *                     which has not fixed sizes
   *
   * @return Height of this element
   */
  double getHeight(final bool dynamicParts) {
    return getAscentHeight(dynamicParts) + getDescentHeight(dynamicParts);
  }
  
  double getRealAscentHeight() {
    return getAscentHeight(true);
  }
  
  /**
   * Return the current height of the upper part of this component from the baseline
   *
   * @param dynamicParts Should be true, if the calculation consider the elements,
   *                     which has not fixed sizes
   *
   * @return Height of the upper part
   */
  double getAscentHeight(final bool dynamicParts) {
    double height = 0.0;

    for (final MathElement element in children)
      height = max(height, element.getAscentHeight(dynamicParts));
    
    return height;
  }

  /**
   * Return the current height of the lower part of this component from the baseline
   *
   * @param dynamicParts Should be true, if the calculation consider the elements,
   *                     which has not fixed sizes
   *
   * @return Height of the lower part
   */
  double getDescentHeight(final bool dynamicParts) {
    double height = 0.0;

    for (final MathElement element in children)
      height = max(height, element.getDescentHeight(dynamicParts));
    
    return height;
  }

  /**
   * Returns the distance of the baseline and the middleline
   *
   * @return Distance
   */
  double getMiddleShift() {
    return base.getFontMetrics(getFontSize()).ascent * 0.30;
    // half the height of a 'x' would be better, but we need a Graphics object to calculate that
    // (the current 0.30 factor depends on the font...)
  }
}



Index: modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_frac.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_frac.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of equations;

/**
 * This math element presents a mathematical fraction
 */
class MathFrac extends MathElement {
  
  /** The XML element from this class */
  static final String ELEMENT = "mfrac";

  /** Attribute name of the linethickness property */
  static final String ATTRIBUTE_LINETHICKNESS = "linethickness"; 

  int _linethickness = 1;

  
  MathFrac() : super();
  
  
  /**
   * Sets the thickness of the fraction line
   *
   * @param linethickness Thickness
   */
  void setLineThickness(final int linethickness) {
    if (linethickness >= 0)
      this._linethickness = linethickness;
  }

  @override
  void paint(final h.CanvasRenderingContext2D context, final double posX, final double posY) {
    final MathElement e1 = getMathElement(0);
    final MathElement e2 = getMathElement(1);

    final double middle = posY - getMiddleShift();

    final double width = getWidth(true);

    e1.paint(context, posX + (width - e1.getWidth(true)) / 2,
        middle - e1.getDescentHeight(true) - 1);
    
    context.lineWidth = _linethickness;
    context.beginPath();
    double y = middle - _linethickness / 2;
    if (_linethickness == 1)
      y = y.floorToDouble() + 0.5;
    context.moveTo(posX + 1, y);
    context.lineTo(posX + width - 1, y);
    context.stroke();
    context.lineWidth = 1;

    e2.paint(context, posX + (width - e2.getWidth(true)) / 2,
        middle + e2.getAscentHeight(true) + 1);
  }

  @override
  double getWidth(final bool dynamicParts) {
    return max(getMathElement(0).getWidth(dynamicParts), getMathElement(1).getWidth(dynamicParts)) + 4;
  }

  @override
  double getHeight(final bool dynamicParts) {
    return getAscentHeight(true) + getDescentHeight(true);
  }

  @override
  double getAscentHeight(final bool dynamicParts) {
    return getMathElement(0).getHeight(true) + 1 + _linethickness / 2 + getMiddleShift();
  }

  @override
  double getDescentHeight(final bool dynamicParts) {
    return max(0, getMathElement(1).getHeight(true) + 1 + _linethickness / 2 - getMiddleShift());
  }
  
}


Index: modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_identifier.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_identifier.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of equations;

/**
 * This class presents a mathematical idenifier, like "x"
 */
class MathIdentifier extends MathText {
  /** The XML element from this class */
  static final String ELEMENT = "mi";

  /** Attribute name of the mathvariant property */
  static final String ATTRIBUTE_MATHVARIANT = "mathvariant"; 

  String _mathvariant = "italic";
  
  
  /**
   * Sets the mathvariant attribute
   *
   * @param mathvariant mathvariant (normal | bold | italic | bold-italic)
   */
  void setMathvariant(final String mathvariant) {
    this._mathvariant = mathvariant;
  }
  
  @override
  void paint(final h.CanvasRenderingContext2D context, final double posX, final double posY) {
    final String s = getText();
    String f;
    if (_mathvariant == 'italic')
      f = getItalicFont();
    else if (_mathvariant == 'bold')
      f = getBoldFont();
    else if (_mathvariant == 'bold-italic')
      f = getBoldItalicFont();
    else
      f = getFont();
    context.font = f;
    context.fillText(s, posX, posY);
  }
  
  @override
  String getFont() {
    if (base != null)
      return base.getFont(fontsize, MathBase.STYLE_ITALIC);
    return null;
  }

}


Index: modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_number.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_number.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of equations;

/**
 * This class presents numbers in a equation
 */
class MathNumber extends MathText {

  /** The XML element from this class */
  static final String ELEMENT = "mn";
  
}


Index: modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_operator.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_operator.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of equations;

/**
 * This class presents a operator, like "(" or "*"
 */
class MathOperator extends MathElement {

  /** The XML element from this class */
  static final String ELEMENT = "mo";

  /** Attribute name of the stretchy property */
  static final String ATTRIBUTE_STRETCHY = "stretchy";
  
  bool _stretchy = true;
  double _lspace = 0.0; // left space, in em
  double _rspace = 0.0; // right space, in em

  
  MathOperator() : super();
  
  
  /**
   * Enables, or disables if the operator should fit his
   * size to the size of the container
   *
   * @param stretchy True, if the operater should fit this size
   */
  void setStretchy(final bool stretchy) {
    this._stretchy = stretchy;
  }
  
  /**
   * Set left space in em
   */
  void setLspace(final double lspace) {
    this._lspace = lspace;
  }
  
  /**
   * Set right space in em
   */
  void setRspace(final double rspace) {
    this._rspace = rspace;
  }
  
  /**
   * Paints a delimitier
   *
   * @param context The graphics context to use for painting 
   * @param posX The first left position for painting 
   * @param posY The position of the baseline
   * @param upperSymbol The symbol for upper edge
   * @param middleSymbol The symbol for middle part
   * @param lowerSymbol The symbol for lower edge
   */
  void paintDelimiter(final h.CanvasRenderingContext2D context, final double posX, final double posY,
                      final String upperSymbol, final String middleSymbol, final String lowerSymbol)
  {
    context.font = getFont();
    final double height = getExprHeight();
    final double aheight = getExprAscentHeight();
    TextMetrics topMetrics = new TextMetrics(upperSymbol, getFont());
    TextMetrics midMetrics = new TextMetrics(middleSymbol, getFont());
    TextMetrics botMetrics = new TextMetrics(lowerSymbol, getFont());
    
    double topHeight = topMetrics.actualBoundingBoxAscent + topMetrics.actualBoundingBoxDescent;
    double topAscent = topMetrics.actualBoundingBoxAscent;
    double midHeight = midMetrics.actualBoundingBoxAscent + midMetrics.actualBoundingBoxDescent;
    double midAscent = midMetrics.actualBoundingBoxAscent;
    double botHeight = botMetrics.actualBoundingBoxAscent + botMetrics.actualBoundingBoxDescent;
    double botAscent = botMetrics.actualBoundingBoxAscent;
    
    if (height - topHeight - botHeight > 0) {
      final double goal = height - topHeight - botHeight + 2;
      double sy = goal / midHeight;
      context.save();
      context.scale(1, sy);
      context.fillText(middleSymbol, posX, (posY - aheight + topHeight)/sy + midAscent);
      context.restore();
    }
    context.fillText(upperSymbol, posX, posY - aheight + topAscent);
    context.fillText(lowerSymbol, posX, posY + height - aheight - (botHeight - botAscent).round());
  }

  /**
   * Paints a delimitier
   *
   * @param context The graphics context to use for painting 
   * @param posX The first left position for painting 
   * @param posY The position of the baseline 
   * @param upperSymbol The symbol for upper edge
   * @param middleSymbol The symbol for middle part
   * @param connectSymbol The symbol for connecting the middle part with the edges
   * @param lowerSymbol The symbol for lower edge
   */
  void paintCurlyDelimiter(final h.CanvasRenderingContext2D context, final double posX, final double posY,
                           final String upperSymbol, final String middleSymbol,
                           final String connectSymbol, final String lowerSymbol)
  {
    final double height = getExprHeight();

    context.fillStyle = 'black';
    context.font = getFont();
//TODO: keep this in a cache, or optimize TextMetrics
    TextMetrics metrics = new TextMetrics(connectSymbol, getFont());
    final double cheight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
    
    final int halfcount = ((height / cheight) / 2).floor();

    for (int i = 1; i < halfcount; i++) {
      context.fillText(connectSymbol, posX, posY - cheight * i);
      context.fillText(connectSymbol, posX, posY + cheight * i);
    }
    context.fillText(middleSymbol, posX, posY);
    context.fillText(upperSymbol, posX, posY - cheight * halfcount);
    context.fillText(lowerSymbol, posX, posY + cheight * halfcount);
  }

  /**
   * Paints a horizontal delimitier
   *
   * @param context The graphics context to use for painting 
   * @param posX The first left position for painting 
   * @param posY The position of the baseline 
   * @param upperSymbol The symbol for upper edge
   * @param middleSymbol The symbol for middle part
   * @param connectSymbol The symbol for connecting the middle part with the edges
   * @param lowerSymbol The symbol for lower edge
   */
  void paintCurlyDelimiterHorizontal(final h.CanvasRenderingContext2D context, final double posX, final double posY,
                                     final String upperSymbol,
                                     final String middleSymbol,
                                     final String connectSymbol,
                                     final String lowerSymbol)
  {
    final double height = getParent().getWidth(true);
    final double middle = posY - getMiddleShift();

    context.save();
    context.fillStyle = 'black';
    context.font = getFont();
// THIS HAS NOT BEEN TESTED
    context.translate(posX, posY);
    context.rotate(PI / 2.0);
    context.translate(-posX, -middle);

    final double ascent = getFontMetrics().ascent - 1;
    int countparts = (height / ascent).floor();
    final int halfcount = countparts ~/ 2;

    for (int i = 1; i < halfcount; i++)
    {
      context.fillText(connectSymbol, posX, posY - ascent * i);
      context.fillText(connectSymbol, posX, posY + ascent * i);
    }
    context.fillText(middleSymbol, posX, posY);
    context.fillText(upperSymbol, posX, posY - ascent * halfcount);
    context.fillText(lowerSymbol, posX, posY + ascent * halfcount);

    context.restore();
  }

  @override
  void paint(final h.CanvasRenderingContext2D context, double posX, final double posY) {
    if (_lspace != 0) {
      final double empix = stringWidth('A');
      posX += _lspace * empix;
    }
    if (getText().length == 1
        && "[{(|)}]\u222B".indexOf(getText()[0]) >= 0
        && _stretchy)
    {
      final double ascent = getParent().getAscentHeight(false);
      final double descent = getParent().getDescentHeight(false);
      final double height = ascent + descent - 1;

      if (getText() == "(")
      {
        if (height < getFontMetrics().height)
        {
          context.font = getFont();
          context.fillText(getText(), posX, posY);
        }
        else
          paintDelimiter(context, posX, posY, '\u239B', '\u239C', '\u239D');
      }
      else if (getText() == ")")
      {
        if (height < getFontMetrics().height)
        {
          context.font = getFont();
          context.fillText(getText(), posX, posY);
        }
        else
          paintDelimiter(context, posX, posY, '\u239E', '\u239F', '\u23A0');
      }
      else if (getText() == "[")
      {
        if (height < getFontMetrics().height)
        {
          context.font = getFont();
          context.fillText(getText(), posX, posY);
        }
        else
          paintDelimiter(context, posX, posY, '\u23A1', '\u23A2', '\u23A3');
      }
      else if (getText() == "]")
      {
        if (height < getFontMetrics().height)
        {
          context.font = getFont();
          context.fillText(getText(), posX, posY);
        }
        else
          paintDelimiter(context, posX, posY, '\u23A4', '\u23A5', '\u23A6');

      }
      else if (getText() == "{")
      {
        if (height < getFontMetrics().height)
        {
          context.font = getFont();
          context.fillText(getText(), posX, posY);
        }
        else
          paintCurlyDelimiter(context, posX, posY, '\u23A7', '\u23A8', '\u23AA', '\u23A9');
      }
      else if (getText() == "}")
      {
        if (height < getFontMetrics().height)
        {
          context.font = getFont();
          context.fillText(getText(), posX, posY);
        }
        else
          paintCurlyDelimiter(context, posX, posY, '\u23AB', '\u23AC', '\u23AA', '\u23AD');
      }
      else if (getText() == "|")
      {
        context.beginPath();
        context.moveTo(posX + 2, posY - ascent);
        context.lineTo(posX + 2, posY + descent);
        context.stroke();
      }
      else if (getText() == "\u222B")
      {
        paintDelimiter(context, posX, posY, '\u2320', '\u23AE', '\u2321');
      }
    }
    else if (getText().length == 1
        && "\uFE37\uFE38".indexOf(getText()[0]) >= 0)
    {
      //final int width = getParent().getWidth(true);
      //final int halfwidth = width / 2;
      //final int height = max((int) (width * 0.2), 2);
      //final int halfheight = height / 2;

      if (getText() == "\uFE37")
        paintCurlyDelimiterHorizontal(context, posX, posY, '\u23A7', '\u23A8', '\u23AA', '\u23A9');
      else if (getText() == "\uFE38")
        paintCurlyDelimiterHorizontal(context, posX, posY, '\u23AB', '\u23AC', '\u23AA', '\u23AD');
    }
    else if (getText().length == 1
        && "\u2211\u220F".indexOf(getText()[0]) >= 0
        && _stretchy)
    {
      context.strokeStyle = 'black';
      context.fillStyle = 'black';
      if (getExprHeight() > getFontMetrics().height) {
        final String bigfont = getMathBase().getFont(getFontSize() * 2);
        context.font = bigfont;
      } else
        context.font = getFont();
      if ("\u2211".indexOf(getText()[0]) >= 0)
        context.fillText("\u2211", posX, posY);
      else
        context.fillText("\u220F", posX, posY);
      context.font = getFont();
    }
    else if (getText() == "\u00AF" && _stretchy) // over bar
    {
      final double width = getParent().getWidth(false) - 2;
      context.beginPath();
      context.moveTo(posX, posY);
      context.lineTo(posX + width, posY);
      context.stroke();
    }
    else if (getText() == "^" && _stretchy) // hat
    {
      final double width = getParent().getWidth(false) - 3;
      context.beginPath();
      context.moveTo(posX, posY);
      context.lineTo(posX + width/2, posY - 3);
      context.moveTo(posX + width/2, posY - 3);
      context.lineTo(posX + width, posY);
      context.stroke();
    }
    else if (getText() == "." || getText() == ".." || getText() == "...")
    {
      context.font = getFont();
      context.fillText(getText(), posX + 1, posY);
    }
    else
    {
      context.font = getFont();
      context.fillText(getText(), posX, posY);
    }
  }
  
  MathElement getExpr() {
    if (getText() == "\u222B" || "\u2211\u220F".contains(getText())) {
      MathRow row;
      if (getParent() is MathRow)
        row = getParent() as MathRow;
      else
        row = getParent().getParent() as MathRow;
      // test intégrale/sommation/produit double
      final MathElement contenu = row.getMathElement(1);
      if (!(contenu is MathRow && contenu.getMathElementCount() > 0))
        return(contenu);
      final MathElement interieur1 = contenu.getMathElement(0);
      if (!(interieur1 is MathRow && interieur1.getMathElementCount() > 0))
        return(contenu);
      final MathElement interieur2 = interieur1.getMathElement(0);
      if (!(interieur2 is MathUnderOver || interieur2 is MathUnder ||
          interieur2 is MathOver || interieur2 is MathOperator))
        return(contenu);
      MathElement op;
      if (interieur2 is MathUnderOver || interieur2 is MathUnder || interieur2 is MathOver)
        op = interieur2.getMathElement(0);
      else
        op = interieur2;
      if (!(op.getText() == "\u222B" || "\u2211\u220F".contains(op.getText())))
        return(contenu);
      return(interieur1.getMathElement(1));
    } else
      return(getParent());
  }
  
  double getExprHeight() {
    return(getExpr().getHeight(false));
  }

  double getExprAscentHeight() {
    return(getExpr().getAscentHeight(false));
  }

  double getExprDescentHeight() {
    return(getExpr().getDescentHeight(false));
  }

  @override
  double getWidth(final bool dynamicParts) {
    double totalspace = 0.0;
    if (_lspace != 0 || _rspace != 0) {
      final double empix = stringWidth('A');
      final double lspacepix = _lspace * empix;
      final double rspacepix = _rspace * empix;
      totalspace = lspacepix + rspacepix;
    }
    if (getText().length == 1) {
      final String firstchar = getText()[0];
      if ("|".indexOf(firstchar) >= 0)
        return 5 + totalspace;
      else if ("\uFE37\uFE38".indexOf(firstchar) >= 0)
        return 1 + totalspace;
      else if ("\u222B".indexOf(firstchar) >= 0) {
        TextMetrics tm = new TextMetrics(getText(), getMathBase().getFont(getFontSize()*2));
        return tm.width + totalspace;
      } else if ("\u2211\u220F".indexOf(firstchar) >= 0) {
        if (getExprHeight() > getFontMetrics().height) {
          TextMetrics tm = new TextMetrics(getText(), getMathBase().getFont(getFontSize()*2));
          return tm.width + totalspace;
        } else
          return stringWidth(getText()).round() + totalspace;
      } else if ("^\u00AF".indexOf(firstchar) >= 0 && _stretchy && dynamicParts)
        return getParent().getWidth(false) - 2;
    }
    return stringWidth(getText()).round() + totalspace;
  }

  @override
  double getHeight(final bool dynamicParts) {
    return getAscentHeight(dynamicParts) + getDescentHeight(dynamicParts);
  }

  @override
  double getAscentHeight(final bool dynamicParts) {
    if (getText().length == 1 && "[()]\u222B".indexOf(getText()[0]) >= 0) {
      if (!dynamicParts || !_stretchy)
        return getFontMetrics().ascent;
      return(getExprAscentHeight() + 1);
    } else if (getText().length == 1 && "{}".indexOf(getText()[0]) >= 0) {
      if (!dynamicParts || !_stretchy)
        return getFontMetrics().ascent;
      final double ascent = getFontMetrics().ascent;
      final int countparts = (getExprHeight() / ascent).floor() + 1;

      if ("{}".indexOf(getText()[0]) >= 0)
        if (countparts % 2 == 0)
          return (countparts + 1) * ascent * 0.5 + getMiddleShift();
      return countparts * ascent * 0.5 + getMiddleShift();
    }
    else if (getText().length == 1
        && "\u2211\u220F".indexOf(getText()[0]) >= 0
        && _stretchy) {
      if (getExprHeight() > getFontMetrics().height)
        return getMathBase().getFontMetrics(getFontSize() * 2).ascent;
      else
        return getFontMetrics().ascent;
    } else if (getText().length == 1
        && "\uFE37\uFE38".indexOf(getText()[0]) >= 0)
      return 0.0;
    else if (getText() == "\u00AF" && _stretchy)
      return(3.0);
    else if (getText() == "\u223C" && _stretchy)
      return((getFontMetrics().ascent ~/ 2).toDouble());
    else if (getText() == "." || getText() == ".." || getText() == "...")
      return((getFontMetrics().ascent ~/ 2).toDouble());
    else
      return getFontMetrics().ascent;
  }

  @override
  double getDescentHeight(final bool dynamicParts) {
    if (getText().length == 1 && "[()]\u222B".indexOf(getText()[0]) >= 0) {
      if (!dynamicParts || !_stretchy)
        return getFontMetrics().descent;
      return(getExprDescentHeight() + 1);
    } else if (getText().length == 1 && "{}".indexOf(getText()[0]) >= 0) {
      if (!dynamicParts || !_stretchy)
        return getFontMetrics().descent;
      final double ascent = getFontMetrics().ascent;
      final int countparts = (getExprHeight() / ascent).floor() + 1;

      if ("{}".indexOf(getText()[0]) >= 0)
        if (countparts % 2 == 0)
          return (countparts + 1) * ascent * 0.5 - getMiddleShift();
      return countparts * ascent * 0.5 - getMiddleShift();
    }
    else if (getText().length == 1
        && "\u2211\u220F".indexOf(getText()[0]) >= 0
        && _stretchy) {
      if (getExprHeight() > getFontMetrics().height)
        return getMathBase().getFontMetrics(getFontSize() * 2).descent;
      else
        return getFontMetrics().descent;
    } else if (getText().length == 1
        && "\uFE37\uFE38".indexOf(getText()[0]) >= 0)
      return stringWidth("}");
    else
      return getFontMetrics().descent;
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_over.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_over.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of equations;

/**
 * This class arrange a element over an other element
 */
class MathOver extends MathElement {

  /** The XML element from this class */
  static final String ELEMENT = "mover";
  
  bool _accent = false;

  
  MathOver() : super();
  

  @override
  void addMathElement(final MathElement child) {
    super.addMathElement(child);
    if (child != null) {
      if (getMathElementCount() == 2 && !_accent)
        child.setFontSize(getFontSize() - 2);
      else
        child.setFontSize(getFontSize());
    }
  }

  @override
  void setFontSize(final int fontsize) {
    super.setFontSize(fontsize);
    if (getMathElement(1) != null && !_accent)
        getMathElement(1).setFontSize(getFontSize()-2);
  }

  @override
  void paint(final h.CanvasRenderingContext2D context, final double posX, final double posY) {
    final MathElement e1 = getMathElement(0);
    final MathElement e2 = getMathElement(1);

    final double width = getWidth(true);

    e1.paint(context, posX + (width - e1.getWidth(true)) / 2, posY);
    if (_accent) {
      double h;
      if (e1 is MathText || e1 is MathIdentifier)
        h = e1.getRealAscentHeight() + 3;
      else
        h = e1.getAscentHeight(true);
      e2.paint(context, posX + (width - e2.getWidth(true)) / 2,
          posY - h);
    } else
      e2.paint(context, posX + (width - e2.getWidth(true)) / 2,
          posY - (e1.getAscentHeight(true) + e2.getDescentHeight(true)));
  }

  @override
  double getWidth(final bool dynamicParts) {
    return max(getMathElement(0).getWidth(dynamicParts),
               getMathElement(1).getWidth(dynamicParts));
  }

  @override
  double getHeight(final bool dynamicParts) {
    if (_accent)
      return getMathElement(0).getHeight(dynamicParts) + getMathElement(1).getAscentHeight(dynamicParts) + 1;
    else
      return getMathElement(0).getHeight(dynamicParts) + getMathElement(1).getHeight(dynamicParts);
  }

  @override
  double getAscentHeight(final bool dynamicParts) {
    if (_accent)
      return getMathElement(0).getAscentHeight(true) + getMathElement(1).getAscentHeight(true);
    else
      return getMathElement(0).getAscentHeight(true) + getMathElement(1).getHeight(true);
  }

  @override
  double getDescentHeight(final bool dynamicParts) {
    return getMathElement(0).getDescentHeight(true);
  }
  
  void setAccent(final bool accent) {
    this._accent = accent;
  }
  
  bool getAccent() {
    return _accent;
  }
  
}


Index: modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_phantom.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_phantom.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of equations;

/**
 * This class represents a phantom of a math element
 * This is used as spacer
 */
class MathPhantom extends MathElement {

  /** The XML element from this class */
  static final String ELEMENT = "mphantom";  

  
  MathPhantom() : super();
  
  
  @override
  void paint(final h.CanvasRenderingContext2D context, final double posX, final double posY) {
  }
  
}


Index: modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_root.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_root.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of equations;

/**
 * This class presents a mathematical root
 */
class MathRoot extends MathElement {

  /** The XML element from this class */
  static final String ELEMENT = "mroot";

  
  
  MathRoot() : super();
  
  
  @override
  void addMathElement(final MathElement child) {
    super.addMathElement(child);
    if (child != null)
    {
      if (getMathElementCount() == 2)
        child.setFontSize(getFontSize() - 2);
      else
        child.setFontSize(getFontSize());
    }
  }

  @override
  void setFontSize(final int fontsize) {
    super.setFontSize(fontsize);
    if (getMathElement(1) != null)
      getMathElement(1).setFontSize(getFontSize() - 2);
  }

  @override
  void paint(final h.CanvasRenderingContext2D context, final double posX, final double posY) {
    if (getMathElementCount() < 2)
      return;

    final MathElement e1 = getMathElement(0);
    final MathElement e2 = getMathElement(1);
    final double width = getWidth(true);

    final double width1 = e1.getWidth(true);
    //final int height1 = e1.getHeight(true);
    final double height1 = 8.0;
    final double width2 = e2.getWidth(true);
    final double height2 = e2.getHeight(true);

    final double aheight1 = e1.getAscentHeight(true);
    final double dheight1 = e1.getDescentHeight(true);
    
    context.beginPath();
    
    context.moveTo(posX, posY);
    context.lineTo(posX + width2, posY);
    
    context.moveTo(posX + width2, posY);
    context.lineTo(posX + height1 / 2 + width2, posY + dheight1);
    
    context.moveTo(posX + width2 - 1, posY);
    context.lineTo(posX + height1 / 2 + width2, posY + dheight1);
    
    context.moveTo(posX + height1 / 2 + width2, posY + dheight1);
    context.lineTo(posX + height1 + width2, posY - (aheight1 + 2));
    
    context.moveTo(posX + height1 + width2, posY - (aheight1 + 2));
    context.lineTo(posX + width, posY - (aheight1 + 2));
    
    context.stroke();
    
    e1.paint(context, posX + height1 + width2, posY);
    e2.paint(context, posX, posY - e2.getDescentHeight(true));
  }

  @override
  double getWidth(final bool dynamicParts) {
    if (getMathElementCount() < 2)
      return 0.0;

    //return getMathElement(0).getWidth(dynamicParts) + getMathElement(0).getHeight(true) + getMathElement(1).getWidth(dynamicParts);
    return getMathElement(0).getWidth(dynamicParts) + 8 + getMathElement(1).getWidth(dynamicParts);
  }

  @override
  double getHeight(final bool dynamicParts) {
    if (getMathElementCount() < 2)
      return 0.0;

    return getDescentHeight(true)
        + max(getMathElement(0).getAscentHeight(true) + 4,
            getMathElement(1).getHeight(true));
  }

  @override
  double getAscentHeight(final bool dynamicParts) {
    if (getMathElementCount() < 2)
      return 0.0;

    return max(getMathElement(0).getAscentHeight(true) + 4,
        getMathElement(1).getHeight(true));
  }

  @override
  double getDescentHeight(final bool dynamicParts) {
    if (getMathElementCount() < 2)
      return 0.0;

    return getMathElement(0).getDescentHeight(true);
  }
  
}


Index: modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_root_element.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_root_element.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of equations;

/**
 * The root element for creating a MathElement tree
 */
class MathRootElement extends MathElement {
  
  /** The XML element from this class */
  static final String ELEMENT = "math"; 

  /** Attribute name of the mode property */
  static final String ATTRIBUTE_MODE = "mode";

  /** Inline mathematical expression */
  static final int INLINE = 0;

  /** Non inline mathematical expression */
  static final int DISPLAY = 1;

  int _mode = INLINE;

  bool _debug = false;

  
  MathRootElement() : super();
  
  
  /**
   * Set the type of equation
   *
   * @param mode INLINE|DISPLAY
   */
  void setMode(final int mode) {
    if ((mode==INLINE) || (mode==DISPLAY))
        this._mode = mode;
  }

  /**
   * Returns the mode
   *
   * @return Display mode
   */
  int getMode() { 
    return _mode;
  }
  
  /**
   * Enables, or disables the debug mode
   *
   * @param debug Debug mode
   */
  void setDebug(final bool debug) {
    this._debug = debug;
  }
  
  /**
   * Indicates, if the debug mode is enabled
   *
   * @return True, if the debug mode is enabled
   */
  bool isDebug() {
    return _debug;
  }
  
  /**
   * Paints this component and all of its elements
   *
   * @param g The graphics context to use for painting
   */
  void paintComponent(final h.CanvasRenderingContext2D context) {
    if (_debug) {
      context.strokeStyle = 'blue';
      
      context.beginPath();
      
      context.moveTo(0, 0);
      context.lineTo(getComponentWidth() - 1, 0);
      
      context.moveTo(getComponentWidth() - 1, 0);
      context.lineTo(getComponentWidth() - 1, getComponentHeight() - 1);
      
      context.moveTo(0, 0);
      context.lineTo(0, getComponentHeight() - 1);
      
      context.moveTo(0, getComponentHeight() - 1);
      context.lineTo(getComponentWidth() - 1, getComponentHeight() - 1);
  
      context.stroke();
      
      context.strokeStyle = 'cyan';
      context.moveTo(0, getComponentHeight() / 2);
      context.lineTo(getComponentWidth() - 1, getComponentHeight() / 2);
      context.stroke();
      
      context.strokeStyle = 'black';
    }
    
    if (getMathElement(0) != null) {
      final double ha = getMathElement(0).getAscentHeight(true) - getMiddleShift();
      final double hb = getMathElement(0).getDescentHeight(true) + getMiddleShift();
      if (ha >= hb)
        paint(context, 0.0, getAscentHeight(true));
      else
        paint(context, 0.0, hb + getMiddleShift());
    }
  }
  
  /**
   * Return the current width of this component
   *
   * @return Width
   */
  int getComponentWidth() {
    return getWidth(true).ceil();
  }
  
  /**
   * Return the current height of this component
   *
   * @return Height
   */
  int getComponentHeight() {
    return getHeight(true).ceil();
  }
  
  @override
  void paint(final h.CanvasRenderingContext2D context, final double posX, final double posY) {
    if (getMathElement(0) != null)
      getMathElement(0).paint(context, posX, posY);
  }

  @override
  double getWidth(final bool dynamicParts) {
    if (getMathElement(0) == null)
      return 0.0;

    return getMathElement(0).getWidth(true) + 1;
  }

  @override
  double getHeight(final bool dynamicParts) {
    if (getMathElement(0) == null)
      return 0.0;

    if (_mode == DISPLAY)
      return getMathElement(0).getHeight(true) + 2;
    
    // CSS vertical-align:middle
    // baseline + height('x')/2
    final double ha = getMathElement(0).getAscentHeight(true) - getMiddleShift();
    final double hb = getMathElement(0).getDescentHeight(true) + getMiddleShift();
    return max(ha, hb) * 2;
  }

  @override
  double getAscentHeight(final bool dynamicParts) {
    if (getMathElement(0) == null)
      return 0.0;

    if (_mode == DISPLAY)
      return getMathElement(0).getAscentHeight(true);
    
    return max(getMathElement(0).getAscentHeight(true),
        getMathElement(0).getDescentHeight(true));
  }

  @override
  double getDescentHeight(final bool dynamicParts) {
    if (getMathElement(0) == null)
      return 0.0;

    if (_mode == DISPLAY)
      return getMathElement(0).getDescentHeight(true);
    
    return max(getMathElement(0).getAscentHeight(true),
        getMathElement(0).getDescentHeight(true));
  }
  
}


Index: modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_row.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_row.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of equations;

/**
 * This class presents a row
 */
class MathRow extends MathElement {

  /** The XML element from this class */
  static final String ELEMENT = "mrow";
  
  
  MathRow() : super();
  
}


Index: modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_sqrt.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_sqrt.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of equations;

/**
 * This class presents a mathematical square root
 */
class MathSqrt extends MathElement {

  /** The XML element from this class */
  static final String ELEMENT = "msqrt";

  
  MathSqrt() : super();
  
  
  @override
  void paint(final h.CanvasRenderingContext2D context, final double posX, final double posY) {
    final double width = getWidth(true);

    final double width1 = getMathElementsWidth();
    //double height1 = getMathElementsHeight(true);
    final double height1 = 8.0;
    final double aheight1 = getMathElementsAscentHeight(true);
    final double dheight1 = getMathElementsDescentHeight(true);
    
    context.beginPath();
    
    context.moveTo(posX, posY);
    context.lineTo(posX + 3, posY - 1);
    
    context.moveTo(posX + 3, posY);
    context.lineTo(posX + height1 / 2 + 1, posY + dheight1);
    
    context.moveTo(posX + 2, posY - 1);
    context.lineTo(posX + height1 / 2 + 1, posY + dheight1);
    
    context.moveTo(posX + height1 / 2 + 1, posY + dheight1);
    context.lineTo(posX + height1 + 3, posY - (aheight1 + 1));
    
    context.moveTo(posX + height1 + 3, posY - (aheight1 + 1));
    context.lineTo(posX + width, posY - (aheight1 + 1));
    
    context.stroke();
    
    double pos = posX + height1 + 3;
    MathElement child;

    for (int i = 0; i < getMathElementCount(); i++) {
      child = getMathElement(i);
      child.paint(context, pos, posY);
      pos += child.getWidth(true);
    }
  }

  /**
   * Returns the width of the children
   *
   * @return Width of childs
   */
  double getMathElementsWidth() {
    double width = 0.0;

    for (int i = 0; i < getMathElementCount(); i++)
      width += getMathElement(i).getWidth(true);
    return width;
  }

  @override
  double getWidth(final bool dynamicParts) {
    //return getMathElementsWidth() + getMathElementsHeight(true) + 2;
    return getMathElementsWidth() + 8 + 3;
  }

  /**
   * Returns the maximal height of the children
   *
   * @param dynamicParts Should be true, if the calculation consider the elements,
   *                     which has not fixed sizes
   *
   * @return Maximal height of children
   */
  double getMathElementsHeight(final bool dynamicParts) {
    double height = 0.0;

    for (int i = 0; i < getMathElementCount(); i++)
      height = max(height, getMathElement(i).getHeight(true));
    
    return height;
  }

  @override
  double getHeight(final bool dynamicParts) {
    return getMathElementsHeight(true) + 4;
  }

  /**
   * Return the maximal height of the upper part from the children 
   *
   * @param dynamicParts Should be true, if the calculation consider the elements,
   *                     which has not fixed sizes
   *
   * @return Maximal height of the upper parts from the children
   */
  double getMathElementsAscentHeight(final bool dynamicParts) {
    double height = 0.0;

    for (int i = 0; i < getMathElementCount(); i++)
      height = max(height, getMathElement(i).getAscentHeight(true));
    
    return height;
  }

  @override
  double getAscentHeight(final bool dynamicParts) {
    return getMathElementsAscentHeight(true) + 2;
  }

/**
 * Return the maximal height of the lower part from the children 
 *
 * @param dynamicParts Should be true, if the calculation consider the elements,
 *                     which has not fixed sizes
 *
 * @return Maximal height of the lower parts from the children
 */
  double getMathElementsDescentHeight(final bool dynamicParts) {
    double height = 0.0;

    for (int i = 0; i < getMathElementCount(); i++)
      height = max(height, getMathElement(i).getDescentHeight(true));
    return height;
  }

  @override
  double getDescentHeight(final bool dynamicParts) {
    return getMathElementsDescentHeight(true) + 2;
  }
  
}


Index: modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_sub.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_sub.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of equations;

/**
 * This class arrange an element lower to an other element
 */
class MathSub extends MathElement {

  /** The XML element from this class */
  static final String ELEMENT = "msub";

  
  MathSub() : super();
  
  
  @override
  void addMathElement(final MathElement child) {
    super.addMathElement(child);
    if (child != null) {
      if (getMathElementCount() == 2)
        child.setFontSize(getFontSize() - 2);
      else
        child.setFontSize(getFontSize());
    }
  }

  @override
  void setFontSize(final int fontsize) {
    super.setFontSize(fontsize);
    if (getMathElement(1) != null)
      getMathElement(1).setFontSize(getFontSize()-2);
  }
  
  @override
  void paint(final h.CanvasRenderingContext2D context, final double posX, final double posY) {
    final MathElement e1 = getMathElement(0);
    final MathElement e2 = getMathElement(1);

    e1.paint(context, posX, posY);
    e2.paint(context, posX + e1.getWidth(true),
        posY + e2.getAscentHeight(true) - getMiddleShift());
  }

  @override
  double getWidth(final bool dynamicParts) {
    return getMathElement(0).getWidth(dynamicParts)
        + getMathElement(1).getWidth(dynamicParts);
  }

  @override
  double getHeight(final bool dynamicParts) {
    return getAscentHeight(true) + getDescentHeight(true);
  }

  @override
  double getAscentHeight(final bool dynamicParts) {
    return getMathElement(0).getAscentHeight(true);
  }

  @override
  double getDescentHeight(final bool dynamicParts) {
    return max(getMathElement(0).getDescentHeight(true),
        getMathElement(1).getHeight(true) - getMiddleShift());
  }
  
}


Index: modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_sub_sup.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_sub_sup.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of equations;

/**
 * This class arange a element lower, and a other
 * elements upper to an element
 */
class MathSubSup extends MathElement {

  /** The XML element from this class */
  static final String ELEMENT = "msubsup";

  
  MathSubSup() : super();
  

  @override
  void addMathElement(final MathElement child) {
    super.addMathElement(child);
    if (child != null) {
      if ((getMathElementCount() == 2) || (getMathElementCount() == 3))
        child.setFontSize(getFontSize() - 2);
      else
        child.setFontSize(getFontSize());
    }
  }

  @override
  void setFontSize(final int fontsize) {
    super.setFontSize(fontsize);
    if (getMathElement(1)!=null)
      getMathElement(1).setFontSize(getFontSize()-2);
    if (getMathElement(2)!=null)
      getMathElement(2).setFontSize(getFontSize()-2);
  }
  
  @override
  void paint(final h.CanvasRenderingContext2D context, final double posX, final double posY) {
    final MathElement e1 = getMathElement(0);
    final MathElement e2 = getMathElement(1);
    final MathElement e3 = getMathElement(2);

    // int middleshift = getMiddleShift();
    final double childmiddleshift = e2.getMiddleShift();
    // int posY1 = Math.max(posY+e2.getAscentHeight(true)-middleshift,
    // posY+e1.getDescentHeight(true)+middleshiftchild);
    // int posY2 = Math.min(posY-(e3.getAscentHeight(true)+middleshift),
    // posY-e1.getAscentHeight(true)+middleshiftchild);
    final double posY1 = posY + e1.getDescentHeight(true) + childmiddleshift / 2;
    final double posY2 = posY - e1.getAscentHeight(true) + childmiddleshift;

    e1.paint(context, posX, posY);
    e2.paint(context, posX + e1.getWidth(true), posY1);
    e3.paint(context, posX + e1.getWidth(true), posY2);
  }

  @override
  double getWidth(final bool dynamicParts) {
    return getMathElement(0).getWidth(dynamicParts)
        + max(getMathElement(1).getWidth(dynamicParts),
            getMathElement(2).getWidth(dynamicParts));
  }

  @override
  double getHeight(final bool dynamicParts) {
    return getAscentHeight(true) + getDescentHeight(true);
  }

  @override
  double getAscentHeight(final bool dynamicParts) {
    return max(getMathElement(0).getAscentHeight(true),
        getMathElement(2).getHeight(true) + getMiddleShift());
  }

  @override
  double getDescentHeight(final bool dynamicParts) {
    return max(getMathElement(0).getDescentHeight(true),
        getMathElement(1).getHeight(true) - getMiddleShift());
  }
  
}


Index: modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_sup.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_sup.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of equations;

/**
 * This class arrange an element lower to an other element
 */
class MathSup extends MathElement {

  /** The XML element from this class */
  static final String ELEMENT = "msup";

  
  MathSup() : super();
  
  
  /**
   * Add a math element as a child
   *
   * @param child Math element
   */
  @override
  void addMathElement(final MathElement child) {
    super.addMathElement(child);
    if (child != null) {
      if (getMathElementCount() == 2)
        child.setFontSize(getFontSize() - 2);
      else
        child.setFontSize(getFontSize());
    }
  }

  /**
   * Sets the font size for this component
   *
   * @param fontsize Font size
   */
  @override
  void setFontSize(final int fontsize) {
    super.setFontSize(fontsize);
    if (getMathElement(1) != null)
      getMathElement(1).setFontSize(getFontSize()-2);
  }

  @override
  void paint(final h.CanvasRenderingContext2D context, final double posX, final double posY) {
    final MathElement e1 = getMathElement(0);
    final MathElement e2 = getMathElement(1);

    e1.paint(context, posX, posY);
    e2.paint(context, posX + e1.getWidth(true),
        posY - max(e2.getDescentHeight(true) + getMiddleShift(),
                   e1.getAscentHeight(true) - e2.getDescentHeight(true)));
  }

  @override
  double getWidth(final bool dynamicParts) {
    return getMathElement(0).getWidth(dynamicParts) +
        getMathElement(1).getWidth(dynamicParts);
  }

  @override
  double getHeight(final bool dynamicParts) {
    return getAscentHeight(true) + getDescentHeight(true);
  }

  @override
  double getAscentHeight(final bool dynamicParts) {
    final MathElement e1 = getMathElement(0);
    final MathElement e2 = getMathElement(1);
    return(e2.getAscentHeight(true) + max(e2.getDescentHeight(true) + getMiddleShift(),
                                          e1.getAscentHeight(true) - e2.getDescentHeight(true)));
  }

  @override
  double getDescentHeight(final bool dynamicParts) {
    return getMathElement(0).getDescentHeight(true);
  }
  
}


Index: modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_table.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_table.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of equations;

/**
 * This class presents a table
 */
class MathTable extends MathElement {

  /** The XML element from this class */
  static final String ELEMENT = "mtable";

  
  MathTable() : super();
  
  
  @override
  void paint(final h.CanvasRenderingContext2D context, final double posX, final double posY) {
    // debug(g, posX, posY);

    int i, j;
    final List<double> maxrowascentheight = new List<double>(getMathElementCount());
    final List<double> maxrowdescentheight = new List<double>(getMathElementCount());

    for (i = 0; i < getMathElementCount(); i++) {
      maxrowascentheight[i] = getMaxRowAscentHeight(i);
      maxrowdescentheight[i] = getMaxRowDescentHeight(i);
    }

    final int maxcolumns = getMaxColumnCount();
    final List<double> maxcolumnwidth = new List<double>(maxcolumns);

    for (i = 0; i < maxcolumns; i++)
      maxcolumnwidth[i] = getMaxColumnWidth(i);

    final double x1 = posX;
    final double y1 = -getHeight(true) / 2 + maxrowascentheight[0] - getMiddleShift();

    double x = x1;
    double y = y1;

    for (i = 0; i < getMathElementCount(); i++) {
      final MathElement row = getMathElement(i);

      x = x1;
      for (j = 0; (j < maxcolumns) && (j < row.getMathElementCount()); j++) {
        // row.getMathElement(j).paint(g, x, posY+y);
        final MathTableData mtd = row.getMathElement(j) as MathTableData;
        if (mtd.getColumnAlign() == "left")
          mtd.paint(context, x + 1, posY + y);
        else if (mtd.getColumnAlign() == "right")
          mtd.paint(context, x + maxcolumnwidth[j] - mtd.getWidth(true), posY + y);
        else
          mtd.paint(context, x + maxcolumnwidth[j] / 2 - mtd.getWidth(true) / 2, posY + y);
        x += maxcolumnwidth[j];
      }

      y += maxrowdescentheight[i];
      if (i < getMathElementCount() - 1)
        y += maxrowascentheight[i + 1];
    }
  }

  /**
   * Returns the maximal ascent height of a row in this table
   *
   * @param row Row     
   *
   * @return Maximal ascent height
   */
  double getMaxRowAscentHeight(final int row) {
    if (row >= getMathElementCount())
      return 0.0;

    final MathElement child = getMathElement(row);
    double height = 0.0;

    for (int i = 0; i < child.getMathElementCount(); i++)
      height = max(height, child.getMathElement(i).getAscentHeight(true));
    return height;
  }

  /**
   * Returns the maximal descent height of a row in this table
   *
   * @param row Row
   *
   * @return Maximal descent height
   */
  double getMaxRowDescentHeight(final int row) {
    if (row >= getMathElementCount())
      return 0.0;

    final MathElement child = getMathElement(row);
    double height = 0.0;

    for (int i = 0; i < child.getMathElementCount(); i++)
      height = max(height, child.getMathElement(i).getDescentHeight(true));
    return height;
  }

  /**
   * Returns the maximal width of a column
   * in this table
   *
   * @param column Column
   *
   * @return Maximal width
   */
  double getMaxColumnWidth(final int column) {
    double width = 0.0;

    for (int i = 0; i < getMathElementCount(); i++)
    {
      final MathElement child = getMathElement(i);

      if (column < child.getMathElementCount())
        width = max(width, child.getMathElement(column).getWidth(true));
    }
    return width + 2;
  }

  /**
   * Returns the maximal count of columns
   *
   * @return Maximal count of columns
   */
  int getMaxColumnCount() {
    int count = 0;

    for (int i = 0; i < getMathElementCount(); i++) {
      final MathElement child = getMathElement(i);

      count = max(count, child.getMathElementCount());
    }
    return count;
  }

  @override
  double getWidth(final bool dynamicParts) {
    double width = 0.0;
    final int maxcolumns = getMaxColumnCount();

    for (int i = 0; i < maxcolumns; i++)
      width += getMaxColumnWidth(i);
    return width;
  }

  @override
  double getHeight(final bool dynamicParts) {
    double height = 0.0;

    for (int i = 0; i < getMathElementCount(); i++)
      height += getMaxRowAscentHeight(i) + getMaxRowDescentHeight(i);
    return height;
  }

  @override
  double getAscentHeight(final bool dynamicParts) {
    return getHeight(true) / 2 + getMiddleShift();
  }

  @override
  double getDescentHeight(final bool dynamicParts) {
    return getHeight(true) / 2 - getMiddleShift();
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_table_data.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_table_data.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of equations;

/**
 * This class presents a cell of a table
 */
class MathTableData extends MathElement {

  /** The XML element from this class */
  static final String ELEMENT = "mtd";  

  String _columnalign = "center";
  
  
  MathTableData() : super();
  
  
  void setColumnAlign(final String s) {
    _columnalign = s;
  }
  
  String getColumnAlign() {
    return _columnalign;
  }
  
}


Index: modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_table_row.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_table_row.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of equations;

/**
 * This class presents a row in MathTable
 */
class MathTableRow extends MathElement {

  /** The XML element from this class */
  static final String ELEMENT = "mtr";

  
  MathTableRow() : super();
  
  
  @override
  void paint(final h.CanvasRenderingContext2D context, final double posX, final double posY) {
    if (getMathBase().isDebug())
      debug(context, posX, posY);

    final double columnwidth = _getMaxColumnWidth();
    double pos = posX;

    for (int i = 0; i < getMathElementCount(); i++) {
      getMathElement(i).paint(context, pos, posY);
      pos += columnwidth;
    }
  }

  /**
   * Returns the maximal width of a column for all columns in this row
   *
   * @return width
   */
  double _getMaxColumnWidth() {
    double width = 0.0;

    for (int i = 0; i < getMathElementCount(); i++)
      width = max(width, getMathElement(i).getWidth(true));
    return width;
  }

  @override
  double getWidth(final bool dynamicParts) {
    return _getMaxColumnWidth() * getMathElementCount();
  }

  @override
  double getHeight(final bool dynamicParts) {
    double height = 0.0;

    for (int i = 0; i < getMathElementCount(); i++)
      height = max(height, getMathElement(i).getHeight(dynamicParts));
    return height;
  }

  @override
  double getAscentHeight(final bool dynamicParts) {
    double height = 0.0;

    for (int i = 0; i < getMathElementCount(); i++)
      height = max(height, getMathElement(i).getAscentHeight(dynamicParts));
    return height;
  }

  @override
  double getDescentHeight(final bool dynamicParts) {
    double height = 0.0;

    for (int i = 0; i < getMathElementCount(); i++)
      height = max(height, getMathElement(i).getDescentHeight(dynamicParts));
    return height;
  }
  
}


Index: modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_text.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_text.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of equations;

/**
 * This class presents text in a equation
 */
class MathText extends MathElement {

  /** The XML element from this class */
  static final String ELEMENT = "mtext";

  MathText() : super();
  
  
  @override
  void paint(final h.CanvasRenderingContext2D context, final double posX, final double posY) {
    context.font = getFont();
    context.fillText(getText(), posX, posY);
  }

  @override
  double getWidth(final bool dynamicParts) {
    return stringWidth(getText().replaceAll(' ', 'A'));
  }

  @override
  double getHeight(final bool dynamicParts) {
    return getFontMetrics().ascent + getFontMetrics().descent;
  }
  
  double getRealAscentHeight() {
    TextMetrics metrics = getStringMetrics(getText());
    return(metrics.actualBoundingBoxAscent);
  }
  
  @override
  double getAscentHeight(final bool dynamicParts) {
    return getFontMetrics().ascent;
  }

  @override
  double getDescentHeight(final bool dynamicParts) {
    return getFontMetrics().descent;
  }
  
}


Index: modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_under.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_under.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of equations;

/**
 * This class arrange an element under an other element
 */
class MathUnder extends MathElement
{

  /** The XML element from this class */
  static final String ELEMENT = "munder";
  
  
  MathUnder() : super();
  
  
  @override
  void addMathElement(final MathElement child) {
    super.addMathElement(child);
    if (child != null) {
      if (getMathElementCount() == 2)
        child.setFontSize(getFontSize() - 2);
      else
        child.setFontSize(getFontSize());
    }
  }

  @override
  void setFontSize(final int fontsize) {
    super.setFontSize(fontsize);
    if (getMathElement(1) != null)
      getMathElement(1).setFontSize(getFontSize()-2);
  }

  @override
  void paint(final h.CanvasRenderingContext2D context, final double posX, final double posY) {
    final MathElement e1 = getMathElement(0);
    final MathElement e2 = getMathElement(1);

    final double width = getWidth(true);

    e1.paint(context, posX + (width - e1.getWidth(true)) / 2, posY);
    e2.paint(context, posX + (width - e2.getWidth(true)) / 2,
        posY + e1.getDescentHeight(true) + e2.getAscentHeight(true));
  }

  @override
  double getWidth(final bool dynamicParts) {
    return max(getMathElement(0).getWidth(dynamicParts),
        getMathElement(1).getWidth(dynamicParts));
  }

  @override
  double getHeight(final bool dynamicParts) {
    return getMathElement(0).getHeight(dynamicParts)
        + getMathElement(1).getHeight(dynamicParts);
  }

  @override
  double getAscentHeight(final bool dynamicParts) {
    return getMathElement(0).getAscentHeight(true);
  }

  @override
  double getDescentHeight(final bool dynamicParts) {
    return getMathElement(0).getDescentHeight(true) +
        getMathElement(1).getHeight(true);
  }
  
}


Index: modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_under_over.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/equations/elements/math_under_over.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of equations;

/**
 * This class arrange an element under, and an other element over
 * an element
 */
class MathUnderOver extends MathElement {

  /** The XML element from this class */
  static final String ELEMENT = "munderover";
  
  
  MathUnderOver() : super();
  
  
  @override
  void addMathElement(final MathElement child) {
    super.addMathElement(child);
    if (child != null) {
      if ((getMathElementCount() == 2) || (getMathElementCount() == 3))
        child.setFontSize(getFontSize() - 2);
      else
        child.setFontSize(getFontSize());
    }
  }

  @override
  void setFontSize(final int fontsize) {
    super.setFontSize(fontsize);
    if (getMathElement(1) != null)
      getMathElement(1).setFontSize(getFontSize()-2);
    if (getMathElement(2) != null)
      getMathElement(2).setFontSize(getFontSize()-2);
  }
  
  @override
  void paint(final h.CanvasRenderingContext2D context, final double posX, final double posY) {
    final MathElement e1 = getMathElement(0);
    final MathElement e2 = getMathElement(1);
    final MathElement e3 = getMathElement(2);

    final double width = getWidth(true);

    e1.paint(context, posX + (width - e1.getWidth(true)) / 2, posY);
    e2.paint(context, posX + (width - e2.getWidth(true)) / 2,
        posY + e1.getDescentHeight(true) + e2.getAscentHeight(true));
    e3.paint(context, posX + (width - e3.getWidth(true)) / 2,
        posY - (e1.getAscentHeight(true) + e3.getDescentHeight(true)));
  }

  @override
  double getWidth(final bool dynamicParts) {
    return max(getMathElement(0).getWidth(dynamicParts),
        max(getMathElement(1).getWidth(dynamicParts),
                 getMathElement(2).getWidth(dynamicParts)));
  }

  @override
  double getHeight(final bool dynamicParts) {
    return getMathElement(0).getHeight(dynamicParts) +
        getMathElement(1).getHeight(dynamicParts) +
        getMathElement(2).getHeight(dynamicParts);
  }

  @override
  double getAscentHeight(final bool dynamicParts) {
    return getMathElement(0).getAscentHeight(true) +
        getMathElement(1).getHeight(true);
  }

  @override
  double getDescentHeight(final bool dynamicParts) {
    return getMathElement(0).getDescentHeight(true) +
        getMathElement(2).getHeight(true);
  }
  
}


Index: modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_anchor.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_anchor.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of nodes;

/**
 * HTML anchor, assumed to have name and href attributes.
 * Jaxe display type: 'anchor'.
 */
class DNAnchor extends DaxeNode {
  String _nameAtt;
  String _hrefAtt;
  
  DNAnchor.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
    _nameAtt = doc.cfg.elementParameterValue(ref, 'nameAtt', 'name');
    _hrefAtt = doc.cfg.elementParameterValue(ref, 'hrefAtt', 'href');
  }
  
  DNAnchor.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
    _nameAtt = doc.cfg.elementParameterValue(ref, 'nameAtt', 'name');
    _hrefAtt = doc.cfg.elementParameterValue(ref, 'hrefAtt', 'href');
  }
  
  @override
  h.Element html() {
    h.SpanElement span = new h.SpanElement();
    span.id = "$id";
    span.classes.add('dn');
    span.classes.add('anchor');
    if (!valid)
      span.classes.add('invalid');
    if (firstChild == null) {
      h.ImageElement img = new h.ImageElement();
      img.src = 'packages/daxe/images/toolbar/anchor.png';
      img.width = 16;
      img.height = 16;
      if (getAttribute(_nameAtt) != null)
        img.title = getAttribute(_nameAtt);
      img.onClick.listen((h.MouseEvent event) => attributeDialog());
      span.append(img);
    } else {
      if (getAttribute(_hrefAtt) != null)
        span.title = getAttribute(_hrefAtt);
      DaxeNode dn = firstChild;
      while (dn != null) {
        span.append(dn.html());
        dn = dn.nextSibling;
      }
      span.onDoubleClick.listen((h.MouseEvent event) => attributeDialog());
    }
    return(span);
  }
  
  @override
  void updateHTMLAfterChildrenChange(List<DaxeNode> changed) {
    super.updateHTML();
  }
  
  @override
  bool get noDelimiter {
    return(true);
  }
  
  static List<x.Element> aRefs() {
    return(doc.cfg.elementsWithType('anchor'));
  }
  
  /**
   * Adds a link at cursor location.
   */
  static void addLink(x.Element ref) {
    Position start = page.getSelectionStart();
    if (start == page.getSelectionEnd()) {
      if (start.dn is DNText) {
        // select word around cursor position
        String text = start.dn.nodeValue;
        int offset = start.dnOffset;
        int p1 = offset;
        if (p1 > 0)
          p1--;
        int p2 = offset;
        while (p1 > 0 && text[p1] != ' ')
          p1--;
        while (p2 < text.length && text[p2] != ' ')
          p2++;
        page.cursor.setSelection(new Position(start.dn, p1), new Position(start.dn, p2));
      }
    }
    doc.insertNewNode(ref, 'element');
  }
  
  /**
   * Adds an anchor at cursor location.
   */
  static void addAnchor(x.Element ref) {
    doc.insertNewNode(ref, 'element');
  }
  
  /**
   * Removes a link at cursor location.
   */
  static void removeLink() {
    Position start = page.getSelectionStart();
    DaxeNode parent = start.dn;
    while (parent != null) {
      if (parent is DNAnchor)
        break;
      parent = parent.parent;
    }
    if (parent == null)
      return;
    DNAnchor anchor = parent;
    if (anchor.firstChild == null) {
      doc.removeNode(anchor);
      page.updateAfterPathChange();
      return;
    }
    parent = anchor.parent;
    UndoableEdit edit = new UndoableEdit.compound(Strings.get('undo.remove_element'));
    DaxeNode content = doc.cloneBetween(new Position(anchor, 0), new Position(anchor, anchor.offsetLength));
    edit.addSubEdit(new UndoableEdit.removeNode(anchor));
    Position insertPos;
    if (anchor.previousSibling is DNText)
      insertPos = new Position(anchor.previousSibling, anchor.previousSibling.offsetLength);
    else
      insertPos = new Position(parent, parent.offsetOf(anchor));
    edit.addSubEdit(doc.insertChildrenEdit(content, insertPos, checkValidity:false));
    doc.doNewEdit(edit);
    page.updateAfterPathChange();
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_area.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_area.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of nodes;

/**
 * Text area. Nodes inside are indented.
 * Jaxe display type: 'zone' (area).
 * 
 * * parameter: `titreAtt`: an attribute that can be used as a title
 * * parameter: `style`: as in DNStyle
 */
class DNArea extends DaxeNode {
  Tag _b1, _b2;
  
  DNArea.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
    _b1 = new Tag(this, Tag.START);
    _b2 = new Tag(this, Tag.END);
  }
  
  DNArea.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
    _b1 = new Tag(this, Tag.START);
    _b2 = new Tag(this, Tag.END);
    
    fixLineBreaks();
  }
  
  @override
  h.Element html() {
    h.DivElement div = new h.DivElement();
    div.id = "$id";
    div.classes.add('dn');
    if (!valid)
      div.classes.add('invalid');
    div.append(_b1.html());
    h.DivElement contents = new h.DivElement();
    contents.classes.add('indent');
    DaxeNode dn = firstChild;
    while (dn != null) {
      contents.append(dn.html());
      dn = dn.nextSibling;
    }
    if (lastChild == null || lastChild.nodeType == DaxeNode.TEXT_NODE)
      contents.appendText('\n');
    //this kind of conditional HTML makes it hard to optimize display updates:
    //we have to override updateHTMLAfterChildrenChange
    // also, it seems that in IE this adds a BR instead of a text node !
    setStyle(contents);
    div.append(contents);
    div.append(_b2.html());
    return(div);
  }
  
  @override
  void updateHTMLAfterChildrenChange(List<DaxeNode> changed) {
    super.updateHTMLAfterChildrenChange(changed);
    h.DivElement contents = getHTMLContentsNode();
    if (contents.nodes.length > 0) {
      h.Node hn = contents.nodes.first;
      while (hn != null) {
        h.Node next = hn.nextNode;
        if (hn is h.Text || hn is h.BRElement)
          hn.remove();
        hn = next;
      }
    }
    if (lastChild == null || lastChild.nodeType == DaxeNode.TEXT_NODE)
      contents.appendText('\n');
  }
  
  @override
  void updateAttributes() {
    h.Element old = getHTMLNode().nodes[0];
    _b1 = new Tag(this, Tag.START, true);
    old.replaceWith(_b1.html());
  }
  
  @override
  h.Element getHTMLContentsNode() {
    return(getHTMLNode().nodes[1]);
  }
  
  @override
  bool newlineAfter() {
    return(true);
  }
  
  @override
  bool newlineInside() {
    return(true);
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_cdata.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_cdata.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of nodes;

/**
 * CDATA section
 */
class DNCData extends DaxeNode {
  Tag _b1, _b2;
  
  DNCData() : super.fromNodeType(DaxeNode.ELEMENT_NODE) {
    _b1 = new Tag(this, Tag.START);
    _b2 = new Tag(this, Tag.END);
  }
  
  DNCData.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
    _b1 = new Tag(this, Tag.START);
    _b2 = new Tag(this, Tag.END);
  }
  
  @override
  h.Element html() {
    h.SpanElement span = new h.SpanElement();
    span.id = "$id";
    span.classes.add('dn');
    span.append(_b1.html());
    h.SpanElement contents = new h.SpanElement();
    DaxeNode dn = firstChild;
    while (dn != null) {
      contents.append(dn.html());
      dn = dn.nextSibling;
    }
    span.append(contents);
    span.append(_b2.html());
    return(span);
  }
  
  @override
  h.Element getHTMLContentsNode() {
    return(getHTMLNode().nodes[1]);
  }
  
  @override
  x.Node toDOMNode(x.Document domDocument) {
    String data = null;
    if (firstChild != null)
      data = firstChild.nodeValue;
    return(domDocument.createCDATASection(data));
  }
  
  @override
  void updateValidity() {
    super.updateValidity();
    parent.updateValidity();
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_comment.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_comment.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of nodes;

/**
 * XML comment
 */
class DNComment extends DaxeNode {
  Tag _b1, _b2;
  
  DNComment() : super.fromNodeType(DaxeNode.ELEMENT_NODE) {
    _b1 = new Tag(this, Tag.START);
    _b2 = new Tag(this, Tag.END);
  }
  
  DNComment.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
    _b1 = new Tag(this, Tag.START);
    _b2 = new Tag(this, Tag.END);
  }
  
  @override
  h.Element html() {
    h.SpanElement span = new h.SpanElement();
    span.id = "$id";
    span.classes.add('dn');
    span.append(_b1.html());
    h.SpanElement contents = new h.SpanElement();
    DaxeNode dn = firstChild;
    while (dn != null) {
      contents.append(dn.html());
      dn = dn.nextSibling;
    }
    span.append(contents);
    span.append(_b2.html());
    span.style.color = "#808080";
    return(span);
  }
  
  @override
  h.Element getHTMLContentsNode() {
    return(getHTMLNode().nodes[1]);
  }
  
  @override
  x.Node toDOMNode(x.Document domDocument) {
    String data = null;
    if (firstChild != null)
      data = firstChild.nodeValue;
    return(domDocument.createComment(data));
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_division.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_division.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of nodes;

/**
 * A large section in the document, delimited with page-wide tags.
 * Nodes inside are indented.
 * Jaxe display type: 'division'.
 * 
 * * parameter: `titreAtt`: an attribute that can be used as a title
 */
class DNDivision extends DaxeNode {
  Tag _b1, _b2;
  
  DNDivision.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
    _b1 = new Tag(this, Tag.START, true);
    _b2 = new Tag(this, Tag.END, true);
  }
  
  DNDivision.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
    _b1 = new Tag(this, Tag.START, true);
    _b2 = new Tag(this, Tag.END, true);
    
    fixLineBreaks();
  }
  
  @override
  h.Element html() {
    h.DivElement div = new h.DivElement();
    div.id = "$id";
    div.classes.add('dn');
    if (!valid)
      div.classes.add('invalid');
    div.append(_b1.html());
    h.DivElement contents = new h.DivElement();
    contents.classes.add('indent');
    DaxeNode dn = firstChild;
    while (dn != null) {
      contents.append(dn.html());
      dn = dn.nextSibling;
    }
    if (lastChild == null || lastChild.nodeType == DaxeNode.TEXT_NODE)
      contents.appendText('\n');
    //this kind of conditional HTML makes it hard to optimize display updates:
    //we have to override updateHTMLAfterChildrenChange
    // also, it seems that in IE this adds a BR instead of a text node !
    div.append(contents);
    div.append(_b2.html());
    return(div);
  }
  
  @override
  void updateHTMLAfterChildrenChange(List<DaxeNode> changed) {
    super.updateHTMLAfterChildrenChange(changed);
    h.DivElement contents = getHTMLContentsNode();
    if (contents.nodes.length > 0) {
      h.Node hn = contents.nodes.first;
      while (hn != null) {
        h.Node next = hn.nextNode;
        if (hn is h.Text || hn is h.BRElement)
          hn.remove();
        hn = next;
      }
    }
    if (lastChild == null || lastChild.nodeType == DaxeNode.TEXT_NODE)
      contents.appendText('\n');
  }
  
  @override
  void updateAttributes() {
    h.Element old = getHTMLNode().nodes[0];
    _b1 = new Tag(this, Tag.START, true);
    old.replaceWith(_b1.html());
  }
  
  @override
  h.Element getHTMLContentsNode() {
    return(getHTMLNode().nodes[1]);
  }
  
  @override
  bool newlineAfter() {
    return(true);
  }
  
  @override
  bool newlineInside() {
    return(true);
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_document.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_document.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of nodes;

/**
 * The document node.
 */
class DNDocument extends DaxeNode {
  
  String xmlVersion;
  String xmlEncoding;
  
  DNDocument() : super.fromNodeType(DaxeNode.DOCUMENT_NODE) {
    xmlVersion = '1.0';
    xmlEncoding = 'UTF-8';
  }
  
  DNDocument.fromNode(x.Node node) : super.fromNode(node, null) {
    xmlVersion = (node as x.Document).xmlVersion;
    xmlEncoding = (node as x.Document).xmlEncoding;
  }
  
  @override
  h.Element html() {
    h.DivElement div = new h.DivElement();
    div.id = "$id";
    div.classes.add('dn');
    DaxeNode dn = firstChild;
    while (dn != null) {
      div.append(dn.html());
      dn = dn.nextSibling;
    }
    if (lastChild == null || lastChild.nodeType == DaxeNode.TEXT_NODE)
      div.appendText('\n');
    return(div);
  }
  
  @override
  x.Node toDOMNode(x.Document domDocument) {
    domDocument.xmlVersion = xmlVersion;
    domDocument.xmlEncoding = xmlEncoding;
    for (DaxeNode dn=firstChild; dn != null; dn=dn.nextSibling) {
      domDocument.appendChild(dn.toDOMNode(domDocument));
    }
    return(domDocument);
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_empty.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_empty.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of nodes;

/**
 * A representation for an empty XML element, using a single tag.
 * Jaxe display type: 'vide' (empty).
 */
class DNEmpty extends DaxeNode {
  Tag _b1;
  
  DNEmpty.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
    _b1 = new Tag(this, Tag.EMPTY);
  }
  
  DNEmpty.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
    _b1 = new Tag(this, Tag.EMPTY);
  }
  
  @override
  h.Element html() {
    h.Element span;
    span = new h.SpanElement();
    span.id = "$id";
    span.classes.add('dn');
    if (!valid)
      span.classes.add('invalid');
    span.append(_b1.html());
    return(span);
  }
  
  @override
  Position firstCursorPositionInside() {
    return(null);
  }
  
  @override
  Position lastCursorPositionInside() {
    return(null);
  }
  
  @override
  h.Element getHTMLContentsNode() {
    return(null);
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_equa_tex_mem.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_equa_tex_mem.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of nodes;

/**
 * An equation using the TeX syntax. The image is encoded in base64
 * and added as text inside the element.
 * Requires a TeX equation server specified in the config file.
 * Jaxe display type: equatexmem, or a plugin with the class `xpages.JEEquaTeXMemoire`.
 * 
 * * parameter: `serveur`: the URL for the tex.php script converting equations into images
 * * parameter: `texteAtt`: the name of the attribute giving the equation text
 * * parameter: `labelAtt`: the name of the attribute giving the image label
 */
class DNEquaTexMem extends DaxeNode {
  h.ImageElement _img;
  String _textAtt;
  String _labelAtt;
  String _server;
  String _data;
  TeXEquationDialog _dlg;
  
  DNEquaTexMem.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
    _textAtt = doc.cfg.elementParameterValue(ref, 'texteAtt', null);
    _labelAtt = doc.cfg.elementParameterValue(ref, 'labelAtt', null);
    _server = doc.cfg.elementParameterValue(ref, 'serveur', null);
  }
  
  DNEquaTexMem.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent, createChildren: false) {
    _textAtt = doc.cfg.elementParameterValue(ref, 'texteAtt', 'texte');
    _server = doc.cfg.elementParameterValue(ref, 'serveur', null);
    if (node.firstChild != null)
      _data = node.firstChild.nodeValue;
    else
      _data = null;
  }
  
  @override
  h.Element html() {
    //assert(doc.filePath != null);
    _img = new h.ImageElement();
    _img.attributes['id'] = "$id";
    _img.attributes['class'] = 'dn';
    if (_data != null) {
      String src = "data:image/png;base64,$_data";
      _img.attributes['src'] = src;
    }
    _img.onLoad.listen((h.Event event) => fixWidth());
    _img.onClick.listen((h.MouseEvent event) => attributeDialog());
    _img.style.verticalAlign = 'middle';
    return(_img);
  }
  
  void fixWidth() {
    if (_img.naturalWidth > 500) {
      _img.width = 500;
    }
  }
  
  @override
  Position firstCursorPositionInside() {
    return(null);
  }
  
  @override
  Position lastCursorPositionInside() {
    return(null);
  }
  
  @override
  void attributeDialog([ActionFunction okfct]) {
    String text = getAttribute(_textAtt);
    String labelValue = getAttribute(_labelAtt);
    _dlg = new TeXEquationDialog(_server, text,
        labelName: _labelAtt, labelValue: labelValue,
        okfct: () => afterAttributeDialog(okfct));
    _dlg.show();
  }
  
  void afterAttributeDialog(ActionFunction okfct) {
    if (okfct != null)
      okfct();
    String text = _dlg.getText();
    String label = _dlg.getLabel();
    if (okfct == null) {
      // existing element
      List<DaxeAttr> new_attributes = new List<DaxeAttr>();
      new_attributes.add(new DaxeAttr(_textAtt, text));
      if (_labelAtt != null && label != '')
        new_attributes.add(new DaxeAttr(_labelAtt, label));
      UndoableEdit edit = new UndoableEdit.changeAttributes(this, new_attributes, updateDisplay:false);
      doc.doNewEdit(edit);
    } else {
      // new element
      setAttribute(_textAtt, text);
      if (_labelAtt != null)
        setAttribute(_labelAtt, label);
    }
    updateData();
  }
  
  @override
  void updateAttributes() {
    updateData();
  }
  
  void updateData() {
    String text = getAttribute(_textAtt);
    getData(text).then((String data) {
      _data = data;
      updateHTML();
      page.moveCursorTo(new Position(parent, parent.offsetOf(this)+1));
    }, onError: (Object error) {
      h.window.alert(error.toString());
    });
  }
  
  Future<String> getData(String text) {
    if (text == null || text == '')
      text = '?';
    Completer<String> completer = new Completer<String>();
    String url = "$_server?${Uri.encodeComponent(text)}";
    // note: setting responseType does not work with synchronous requests
    // warning: arraybuffer is not supported by Internet Explorer before IE10
    h.HttpRequest.request(url, method: 'GET', responseType: 'arraybuffer'
        ).then((h.HttpRequest request) {
      Object response = request.response;
      // note : ByteBuffer and Uint8List are in dart:typed_data
      if (response is ByteBuffer) {
        Uint8List bytes = new Uint8List.view(response);
        String data = CryptoUtils.bytesToBase64(bytes);
        completer.complete(data);
      } else
        completer.completeError(new DaxeException('request response is not a ByteBuffer'));
    }, onError: (Object error) {
      completer.completeError(new DaxeException("Error getting the equation image from the server $_server : $error"));
    });
    return(completer.future);
  }
  
  @override
  x.Node toDOMNode(x.Document domDocument) {
    x.Element el = domDocument.createElementNS(namespaceURI, nodeName);
    for (DaxeAttr att in attributes)
      el.setAttributeNS(att.namespaceURI, att.name, att.value);
    if (_data != null)
      el.appendChild(domDocument.createTextNode(_data));
    return(el);
  }
}


/**
 * Dialog for DNEquaTexMem
 */
class TeXEquationDialog {
  String _equationText;
  String _labelName;
  String _labelValue;
  ActionFunction _okfct;
  String _server;
  String _data;
  
  
  TeXEquationDialog(this._server, String equationText,
      {String labelName, String labelValue, ActionFunction okfct}) {
    _equationText = equationText;
    _labelName = labelName;
    _labelValue = labelValue;
    _okfct = okfct;
  }
  
  void show() {
    h.DivElement div1 = new h.DivElement();
    div1.id = 'dlg1';
    div1.classes.add('dlg1');
    h.DivElement div2 = new h.DivElement();
    div2.classes.add('dlg2');
    h.DivElement div3 = new h.DivElement();
    div3.classes.add('dlg3');
    h.FormElement form = new h.FormElement();
    
    h.ImageElement img = new h.ImageElement();
    img.id = 'eqimg';
    img.src = getImgSrc();
    form.append(img);
    
    h.TextAreaElement ta = new h.TextAreaElement();
    ta.id = 'eqtext';
    if (_equationText != null)
      ta.value = _equationText;
    ta.style.width = '100%';
    ta.style.height = '4em';
    ta.attributes['spellcheck'] = 'false';
    ta.onInput.listen((h.Event event) => checkReturn());
    form.append(ta);
    
    if (_labelName != null) {
      h.DivElement div_label = new h.DivElement();
      h.SpanElement label_name = new h.SpanElement();
      label_name.text = _labelName;
      div_label.append(label_name);
      div_label.appendText(' ');
      h.TextInputElement input_label = new h.TextInputElement();
      input_label.id = 'eqlabel';
      if (_labelValue != null)
        input_label.value = _labelValue;
      div_label.append(input_label);
      form.append(div_label);
    }
    
    h.DivElement div_preview = new h.DivElement();
    h.ButtonElement bPreview = new h.ButtonElement();
    bPreview.appendText(Strings.get("equation.preview"));
    bPreview.onClick.listen((h.MouseEvent event) => preview(event));
    div_preview.append(bPreview);
    form.append(div_preview);
    
    h.DivElement div_buttons = new h.DivElement();
    div_buttons.classes.add('buttons');
    h.ButtonElement bCancel = new h.ButtonElement();
    bCancel.attributes['type'] = 'button';
    bCancel.appendText(Strings.get("button.Cancel"));
    bCancel.onClick.listen((h.MouseEvent event) => div1.remove());
    div_buttons.append(bCancel);
    h.ButtonElement bOk = new h.ButtonElement();
    bOk.attributes['type'] = 'submit';
    bOk.appendText(Strings.get("button.OK"));
    bOk.onClick.listen((h.MouseEvent event) => ok(event));
    div_buttons.append(bOk);
    form.append(div_buttons);
    
    div3.append(form);
    div2.append(div3);
    div1.append(div2);
    h.document.body.append(div1);
    
    ta.focus();
  }
  
  void ok(h.MouseEvent event) {
    h.TextAreaElement ta = h.querySelector('textarea#eqtext');
    _equationText = ta.value;
    // we can't always get the image data synchronously, this will be done later
    if (_labelName != null) {
      h.TextInputElement input_label = h.querySelector('input#eqlabel');
      _labelValue = input_label.value;
    }
    h.querySelector('div#dlg1').remove();
    if (event != null)
      event.preventDefault();
    if (_okfct != null)
      _okfct();
  }
  
  String getText() {
    return(_equationText);
  }
  
  void updateData() {
    if (_equationText == '')
      return(null);
    h.ImageElement img = h.querySelector('img#eqimg');
    h.CanvasElement canvas = new h.CanvasElement(width: img.width, height: img.height);
    h.CanvasRenderingContext2D context = canvas.context2D;
    context.drawImage(img, 0, 0);
    String dataurl = canvas.toDataUrl('image/png');
    _data = dataurl.substring('data:image/png;base64,'.length);
  }
  
  String getLabel() {
    return(_labelValue);
  }
  
  String getImgSrc() {
    String text = _equationText;
    if (text == null || text == '')
      text = '?';
    return("$_server?${Uri.encodeComponent(text)}");
  }
  
  void updateImg() {
    h.ImageElement img = h.querySelector('img#eqimg');
    img.src = getImgSrc();
  }
  
  void preview(h.MouseEvent event) {
    event.preventDefault();
    h.TextAreaElement ta = h.querySelector('textarea#eqtext');
    _equationText = ta.value;
    updateImg();
  }
  
  void checkReturn() {
    h.TextAreaElement ta = h.querySelector('textarea#eqtext');
    String text = ta.value;
    if (text.length > 0 && text.contains('\n')) {
      ta.value = text.replaceAll('\n', '');
      ok(null);
      return;
    }
  }
}




Index: modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_equation_mem.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_equation_mem.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of nodes;

/**
 * An equation using the Jaxe syntax. The image is encoded in base64
 * and added as text inside the element.
 * Jaxe display type: equationmem, or a plugin with the class `xpages.JEEquationMemoire`.
 * 
 * * parameter: `texteAtt`: the name of the attribute giving the equation text
 * * parameter: `labelAtt`: the name of the attribute giving the image label
 */
class DNEquationMem extends DaxeNode {
  h.ImageElement _img;
  String _textAtt;
  String _data;
  EquationDialog _dlg;
  
  
  DNEquationMem.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
    _textAtt = doc.cfg.elementParameterValue(ref, 'texteAtt', 'src');
    MathBase.loadFonts();
  }
  
  DNEquationMem.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent, createChildren: false) {
    _textAtt = doc.cfg.elementParameterValue(ref, 'texteAtt', 'src');
    if (node.firstChild != null)
      _data = node.firstChild.nodeValue.replaceAll('\n', '');
    else
      _data = null;
    MathBase.loadFonts();
  }
  
  @override
  h.Element html() {
    //assert(doc.filePath != null);
    _img = new h.ImageElement();
    _img.attributes['id'] = "$id";
    _img.attributes['class'] = 'dn';
    if (_data != null) {
      String src = "data:image/png;base64,$_data";
      _img.attributes['src'] = src;
    }
    _img.onLoad.listen((h.Event event) => fixWidth());
    _img.onClick.listen((h.MouseEvent event) => attributeDialog());
    _img.style.verticalAlign = 'middle';
    return(_img);
  }
  
  void fixWidth() {
    if (_img.naturalWidth > 500) {
      _img.width = 500;
    }
  }
  
  @override
  Position firstCursorPositionInside() {
    return(null);
  }
  
  @override
  Position lastCursorPositionInside() {
    return(null);
  }
  
  @override
  void attributeDialog([ActionFunction okfct]) {
    String text = getAttribute(_textAtt);
    if (text == null)
      text = '';
    _dlg = new EquationDialog(text, okfct:() {
      String text = _dlg.getText();
      if (getHTMLNode() != null) {
        DaxeAttr attr = new DaxeAttr(_textAtt, text);
        UndoableEdit edit = new UndoableEdit.changeAttribute(this, attr, updateDisplay:false);
        doc.doNewEdit(edit);
      } else {
        setAttribute(_textAtt, text);
      }
      _data = _dlg.getData();
      if (_img != null) {
        String src = "data:image/png;base64,$_data";
        _img.attributes['src'] = src;
      }
      if (okfct != null)
        okfct();
      
      //delayed cursor update (the image is still loading)
      _img.onLoad.listen((h.Event e) => page.moveCursorTo(new Position(parent, parent.offsetOf(this)+1)));
    });
    _dlg.show();
  }
  
  @override
  void updateAttributes() {
    // this is called whenever the attributes change, for instance after undo/redo,
    // except after attributeDialog because the data and HTML are already updated
    updateData();
    updateHTML();
  }
  
  void updateData() {
    String equationText = getAttribute(_textAtt);
    StringMathBuilder sb = new StringMathBuilder(equationText);
    h.CanvasElement canvas = new h.CanvasElement(width:500, height: 300);
    MathBase _base = new MathBase(element:sb.getMathRootElement(), context:canvas.context2D);
    h.CanvasElement canvas2 = new h.CanvasElement(width:_base.getWidth(), height:_base.getHeight());
    MathBase base2 = new MathBase(element:sb.getMathRootElement(), context:canvas2.context2D);
    base2.paint(canvas2.context2D);
    String dataurl = canvas2.toDataUrl('image/png');
    _data = dataurl.substring('data:image/png;base64,'.length);
  }
  
  @override
  x.Node toDOMNode(x.Document domDocument) {
    x.Element el = domDocument.createElementNS(namespaceURI, nodeName);
    for (DaxeAttr att in attributes)
      el.setAttributeNS(att.namespaceURI, att.name, att.value);
    if (_data != null) {
      StringBuffer sb = new StringBuffer();
      String towrite = _data;
      while (towrite != '') {
        if (towrite.length <= 76) {
          sb.write(towrite);
          towrite = '';
        } else {
          sb.writeln(towrite.substring(0, 76));
          towrite = towrite.substring(76);
        }
      }
      el.appendChild(domDocument.createTextNode(sb.toString()));
    }
    return(el);
  }
}



Index: modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_file.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_file.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of nodes;

/**
 * Image file. The image is displayed in the text if it is found,
 * otherwise an error message is displayed instead of the image.
 * Jaxe display type: 'fichier' (file).
 * 
 * * parameter: `srcAtt`: the name of the attribute giving the file path
 */
class DNFile extends DaxeNode {
  h.ImageElement _img; // TODO: support non-image files
  String _srcAtt;
  
  DNFile.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
    _srcAtt = doc.cfg.elementParameterValue(ref, 'srcAtt', 'src');
  }
  
  DNFile.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
    _srcAtt = doc.cfg.elementParameterValue(ref, 'srcAtt', 'src');
  }
  
  @override
  h.Element html() {
    assert(doc.filePath != null);
    _img = new h.ImageElement();
    _img.id = "$id";
    _img.classes.add('dn');
    String folder = '';
    String xmlFilePath = doc.filePath;
    int ind = xmlFilePath.lastIndexOf('/');
    if (ind != -1)
      folder = xmlFilePath.substring(0, ind + 1);
    String src = "$folder${getAttribute(_srcAtt)}";
    _img.src = src;
    _img.alt = getAttribute(_srcAtt);
    _img.onLoad.listen((h.Event event) => imageLoaded());
    _img.onClick.listen((h.MouseEvent event) => attributeDialog());
    return(_img);
  }
  
  void imageLoaded() {
    // fix the width
    if (_img.naturalWidth > 500) {
      _img.width = 500;
    }
    
    // fix cursor position if it is after the image
    if (page.getSelectionStart() != null &&
        page.getSelectionStart() > new Position(this, 0))
      page.cursor.updateCaretPosition(false);
  }
  
  @override
  Position firstCursorPositionInside() {
    return(null);
  }
  
  @override
  Position lastCursorPositionInside() {
    return(null);
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_form.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_form.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of nodes;

/**
 * Recursive display of all descendants in a single form.
 * Jaxe display type: 'formulaire' (form).
 */
class DNForm extends DaxeNode {
  
  List<x.Element> childrenRefs;
  List<x.Element> attRefs;
  HashMap<String, SimpleTypeControl> attributeControls;
  bool simpleField;
  SimpleTypeControl control; // used for simple fields only
  
  DNForm.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
    childrenRefs = doc.cfg.subElements(ref);
    attRefs = doc.cfg.elementAttributes(ref);
    simpleField = (childrenRefs.length == 0 && attRefs.length == 0);
    init();
  }
  
  DNForm.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent, createChildren: false) {
    childrenRefs = doc.cfg.subElements(ref);
    attRefs = doc.cfg.elementAttributes(ref);
    simpleField = (childrenRefs.length == 0 && attRefs.length == 0);
    // manual children addition in order to use ParentUpdatingDNText instead of DNText
    // whenever the text is changed (for instance with find/replace or undo/redo),
    // DNSimpleType.updateHTML is called in order to update the control
    if (node.childNodes != null) {
      DaxeNode prev = null;
      for (x.Node n in node.childNodes) {
        DaxeNode dn;
        if (simpleField && n.nodeType == x.Node.TEXT_NODE)
          dn = new ParentUpdatingDNText.fromNode(n, this);
        else
          dn = NodeFactory.createFromNode(n, this);
        if (prev == null)
          firstChild = dn;
        else
          prev.nextSibling = dn;
        prev = dn;
      }
    }
    if (!simpleField) {
      // remove all text nodes:
      List<DaxeNode> children = childNodes;
      for (DaxeNode dn in children) {
        if (dn is DNText)
          removeChild(dn);
      }
    }
    init();
  }
  
  void init() {
    if (attRefs.length > 0)
      attributeControls = new HashMap<String, SimpleTypeControl>();
    
    addMissingChildren();
  }
  
  void addMissingChildren() {
    DaxeNode currentChild = null;
    for (x.Element childRef in childrenRefs) {
      bool found = false;
      for (DaxeNode dn in childNodes) {
        if (dn.ref == childRef) {
          found = true;
          currentChild = dn;
        } 
      }
      if (!found) {
        DaxeNode dn = NodeFactory.create(childRef);
        // note: this is not undoable, but will not be serialized if no content is added
        if (currentChild != null)
          insertAfter(dn, currentChild);
        else if (firstChild != null)
          insertBefore(dn, firstChild);
        else
          appendChild(dn);
        currentChild = dn;
      }
    }
  }
  
  @override
  h.Element html() {
    h.ButtonElement bHelp = makeHelpButton(ref, null);
    
    if (simpleField) {
      h.TableRowElement tr = new h.TableRowElement();
      tr.id = "$id";
      tr.classes.add('dn');
      
      h.TableCellElement td = new h.TableCellElement();
      td.classes.add('shrink');
      td.append(bHelp);
      tr.append(td);
      
      td = new h.TableCellElement();
      td.classes.add('shrink');
      td.text = doc.cfg.elementTitle(ref);
      if (doc.cfg.requiredElement(parent.ref, ref))
        td.classes.add('required');
      else
        td.classes.add('optional');
      tr.append(td);
      
      td = new h.TableCellElement();
      /*
      td.classes.add('form_field');
      if (firstChild != null)
        td.append(firstChild.html());
      */
      // with a SimpleTypeControl instead, for validation:
      String value;
      if (firstChild != null)
        value = firstChild.nodeValue;
      else
        value = '';
      control = new SimpleTypeControl.forElement(ref, value, valueChanged: () => changeElementValue());
      td.append(control.html());

      tr.append(td);
      
      if (parent is DNForm)
        (parent as DNForm).addPlusMinusButtons(tr, this);
      
      return(tr);
      
    } else {
      h.DivElement div = new h.DivElement();
      div.id = "$id";
      div.classes.add('dn');
      div.classes.add('form');
      
      h.SpanElement spanTitle = new h.SpanElement();
      spanTitle.classes.add('form_title');
      spanTitle.append(bHelp);
      spanTitle.appendText(' ');
      spanTitle.appendText(doc.cfg.elementTitle(ref));
      if (doc.cfg.requiredElement(parent.ref, ref))
        spanTitle.classes.add('required');
      else
        spanTitle.classes.add('optional');
      div.append(spanTitle);
      
      h.TableElement table = new h.TableElement();
      table.classes.add('expand');
      for (x.Element refAttr in attRefs) {
        table.append(attributeHTML(refAttr));
      }
      for (x.Element childRef in childrenRefs) {
        for (DaxeNode dn in childNodes) {
          if (dn.ref == childRef) {
            dn.userCannotRemove = true;
            addChildHTML(table, dn);
          } 
        }
      }
      div.append(table);
      
      return(div);
    }
  }
  
  void changeElementValue() {
    String value = control.getValue();
    UndoableEdit edit = new UndoableEdit.compound(Strings.get('form.text_edition'));
    /*
    this was causing problems with undo/redo on DNText (the text was not updated)
    (same problem as DNForm.changeElementValue())
    This is solved by using ParentUpdatingDNText
    if (value != '')
      edit.addSubEdit(new UndoableEdit.insertString(new Position(this, 0), value, updateDisplay: false));
    if (firstChild is DNText) {
      edit.addSubEdit(new UndoableEdit.removeString(new Position(firstChild, value.length),
          firstChild.nodeValue.length, updateDisplay: false));
    }
    */
    //this is using ParentUpdatingDNText nodes instead of DNText to avoid the problem
    if (firstChild is DNText)
      edit.addSubEdit(new UndoableEdit.removeNode(firstChild, updateDisplay: false));
    if (value != '')
      edit.addSubEdit(new UndoableEdit.insertNode(new Position(this, 0), new ParentUpdatingDNText(value), updateDisplay: false));
    doc.doNewEdit(edit);
    updateButtons();
  }
  
  @override
  h.Element getHTMLContentsNode() {
    if (simpleField) {
      return(getHTMLNode().nodes[2]); // 3rd td
    } else {
      return(getHTMLNode().nodes[1]); // the table
    }
  }
  
  h.TableRowElement attributeHTML(x.Element refAttr) {
    String name = doc.cfg.attributeQualifiedName(ref, refAttr);
    h.TableRowElement tr = new h.TableRowElement();
    
    h.TableCellElement td = new h.TableCellElement();
    td.classes.add('shrink');
    h.ButtonElement bHelp = makeHelpButton(ref, refAttr);
    td.append(bHelp);
    tr.append(td);
    
    td = new h.TableCellElement();
    td.classes.add('shrink');
    td.text = doc.cfg.attributeTitle(ref, refAttr);
    if (doc.cfg.requiredAttribute(ref, refAttr))
      td.classes.add('required');
    else
      td.classes.add('optional');
    tr.append(td);
    
    td = new h.TableCellElement();
    td.classes.add('expand');
    String value = getAttribute(name);
    String defaultValue = doc.cfg.defaultAttributeValue(refAttr);
    if (value == null) {
      if (defaultValue != null)
        value = defaultValue;
      else
        value = '';
    }
    SimpleTypeControl attributeControl;
    attributeControl = new SimpleTypeControl.forAttribute(ref, refAttr, value,
        valueChanged: () => changeAttributeValue(refAttr, attributeControl), catchUndo: true);
    attributeControls[name] = attributeControl;
    h.Element ht = attributeControl.html();
    if (ht.firstChild is h.TextInputElement)
      (ht.firstChild as h.TextInputElement).classes.add('form_field');
    td.append(ht);
    tr.append(td);
    
    td = new h.TableCellElement();
    td.classes.add('shrink');
    tr.append(td);
    
    return(tr);
  }
  
  void changeAttributeValue(x.Element refAttr, SimpleTypeControl attributeControl) {
    String value = attributeControl.getValue();
    String name = doc.cfg.attributeQualifiedName(ref, refAttr);
    String defaultValue = doc.cfg.defaultAttributeValue(refAttr);
    DaxeAttr attr;
    if ((value == '' && defaultValue == null) || value == defaultValue)
      attr = new DaxeAttr(name, null); // remove the attribute
    else if (value != '' || defaultValue != null)
      attr = new DaxeAttr(name, value);
    else
      attr = null;
    if (attr != null)
      doc.doNewEdit(new UndoableEdit.changeAttribute(this, attr, updateDisplay: false));
  }
  
  void addChildHTML(h.TableElement table, DaxeNode dn) {
    h.Element childHTML = dn.html();
    h.TableRowElement tr;
    if (childHTML is h.TableRowElement)
      tr = childHTML;
    else {
      tr = new h.TableRowElement();
      h.TableCellElement td;
      int colspan;
      if (dn is! DNForm) {
        td = new h.TableCellElement();
        h.ButtonElement bHelp = makeHelpButton(dn.ref, null);
        td.append(bHelp);
        tr.append(td);
        colspan = 2;
      } else
        colspan = 3;
      td = new h.TableCellElement();
      td.colSpan = colspan;
      td.append(childHTML);
      tr.append(td);
      addPlusMinusButtons(tr, dn);
    }
    table.append(tr);
  }
  
  void addPlusMinusButtons(h.TableRowElement tr, DaxeNode dn) {
    h.TableCellElement td = new h.TableCellElement();
    td.classes.add('shrink');
    if (doc.cfg.getSchema().multipleChildren(ref, dn.ref)) {
      if (dn.nextSibling == null || dn.nextSibling.ref != dn.ref) { // last node with this reference
        if (dn.firstChild != null || dn is! DNForm) {
          h.ButtonElement bPlus = new h.ButtonElement();
          bPlus.attributes['type'] = 'button';
          bPlus.value = '+';
          bPlus.text = '+';
          bPlus.onClick.listen((h.Event event) => addFormChild(dn));
          td.append(bPlus);
        }
      }
      // - minus button only if there is another similar sibling
      if ((dn.previousSibling != null && dn.previousSibling.ref == dn.ref) ||
          (dn.nextSibling != null && dn.nextSibling.ref == dn.ref)) {
        h.ButtonElement bMinus = new h.ButtonElement();
        bMinus.attributes['type'] = 'button';
        bMinus.value = '-';
        bMinus.text = '-';
        bMinus.onClick.listen((h.Event event) => doc.removeNode(dn));
        td.append(bMinus);
      }
    }
    tr.append(td);
  }
  
  void addFormChild(DaxeNode prev) {
    DaxeNode dn = NodeFactory.create(prev.ref);
    doc.insertNode(dn, new Position(this, offsetOf(prev) + 1));
  }
  
  @override
  void attributeDialog([ActionFunction okfct]) {
    if (okfct != null)
      okfct();
  }
  
  @override
  void updateHTML() {
    if (!simpleField) {
      super.updateHTML();
      return;
    }
    
    String value;
    if (firstChild != null)
      value = firstChild.nodeValue;
    else
      value = '';
    if (control.getValue() != value)
      control.setValue(value);
    
    updateButtons();
  }
  
  @override
  void updateHTMLAfterChildrenChange(List<DaxeNode> changed) {
    updateHTML();
  }
  
  void updateButtons() {
    if (parent is DNForm) {
      // update plus/minus buttons
      h.TableRowElement tr = getHTMLNode();
      tr.nodes.last.remove();
      (parent as DNForm).addPlusMinusButtons(tr, this);
    }
  }
  
  @override
  void updateAttributes() {
    h.DivElement div = getHTMLNode();
    h.TableElement table = h.querySelector("#$id>table");
    int i = 0;
    for (x.Element refAttr in attRefs) {
      String name = doc.cfg.attributeQualifiedName(ref, refAttr);
      String value = getAttribute(name);
      String defaultValue = doc.cfg.defaultAttributeValue(refAttr);
      if (value == null) {
        if (defaultValue != null)
          value = defaultValue;
        else
          value = '';
      }
      attributeControls[name].setValue(value);
      i++;
    }
  }

  @override
  bool newlineAfter() {
    return(childrenRefs.length != 0);
  }
  
  @override
  x.Node toDOMNode(x.Document domDocument) {
    if (childrenRefs.length == 0)
      return(super.toDOMNode(domDocument));
    
    x.Element el = domDocument.createElementNS(namespaceURI, nodeName);
    for (DaxeAttr att in attributes)
      el.setAttributeNS(att.namespaceURI, att.name, att.value);
    el.appendChild(domDocument.createTextNode('\n'));
    for (DaxeNode dn=firstChild; dn != null; dn=dn.nextSibling) {
      // important change here (empty elements are ignored):
      if (dn.firstChild != null || (dn.attributes != null && dn.attributes.length > 0)) {
        el.appendChild(dn.toDOMNode(domDocument));
        el.appendChild(domDocument.createTextNode('\n'));
      }
    }
    return(el);
  }
  
  static h.ButtonElement makeHelpButton(final x.Element elementRef, final x.Element attributeRef) {
    h.ButtonElement bHelp = new h.ButtonElement();
    bHelp.attributes['type'] = 'button';
    bHelp.classes.add('help');
    bHelp.value = '?';
    bHelp.text = '?';
    if (attributeRef == null) {
      String title = doc.cfg.documentation(elementRef);
      if (title != null)
        bHelp.title = title;
      bHelp.onClick.listen((h.Event event) => (new HelpDialog.Element(elementRef)).show());
    } else {
      String title = doc.cfg.attributeDocumentation(elementRef, attributeRef);
      if (title != null)
        bHelp.title = title;
      bHelp.onClick.listen((h.Event event) => (new HelpDialog.Attribute(attributeRef, elementRef)).show());
    }
    return(bHelp);
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_form_field.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_form_field.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of nodes;

/**
 * Form field.
 * Jaxe display type: 'champ' (field).
 */
class DNFormField extends DaxeNode {
  bool simpleField;
  SimpleTypeControl control; // used for simple fields only
  
  DNFormField.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
    List<x.Element> childrenRefs = doc.cfg.subElements(ref);
    simpleField = (childrenRefs.length == 0);
  }
  
  DNFormField.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent, createChildren: false) {
    List<x.Element> childrenRefs = doc.cfg.subElements(ref);
    simpleField = (childrenRefs.length == 0);
    // manual children addition in order to use ParentUpdatingDNText instead of DNText
    // whenever the text is changed (for instance with find/replace or undo/redo),
    // DNSimpleType.updateHTML is called in order to update the control
    if (node.childNodes != null) {
      DaxeNode prev = null;
      for (x.Node n in node.childNodes) {
        DaxeNode dn;
        if (simpleField && n.nodeType == x.Node.TEXT_NODE)
          dn = new ParentUpdatingDNText.fromNode(n, this);
        else
          dn = NodeFactory.createFromNode(n, this);
        if (prev == null)
          firstChild = dn;
        else
          prev.nextSibling = dn;
        prev = dn;
      }
    }
  }
  
  @override
  h.Element html() {
    h.ButtonElement bHelp = DNForm.makeHelpButton(ref, null);
    
    h.TableRowElement tr = new h.TableRowElement();
    tr.id = "$id";
    tr.classes.add('dn');
    
    h.TableCellElement td = new h.TableCellElement();
    td.classes.add('shrink');
    td.append(bHelp);
    tr.append(td);
    
    td = new h.TableCellElement();
    td.classes.add('shrink');
    td.text = doc.cfg.elementTitle(ref);
    if (doc.cfg.requiredElement(parent.ref, ref))
      td.classes.add('required');
    else
      td.classes.add('optional');
    tr.append(td);
    
    td = new h.TableCellElement();
    td.classes.add('expand');
    if (simpleField) {
      String value;
      if (firstChild != null)
        value = firstChild.nodeValue;
      else
        value = '';
      control = new SimpleTypeControl.forElement(ref, value, valueChanged: () => changeElementValue());
      h.Element ht = control.html();
      ht.id = "content_$id";
      h.TextInputElement input = ht.querySelector('input');
      if (input != null)
        input.classes.add('form_field');
      td.append(ht);
    } else {
      h.DivElement div = new h.DivElement();
      div.id = "content_$id";
      div.classes.add('form_field');
      DaxeNode dn = firstChild;
      while (dn != null) {
        div.append(dn.html());
        dn = dn.nextSibling;
      }
      td.append(div);
    }
    tr.append(td);
    
    if (parent is DNForm)
      (parent as DNForm).addPlusMinusButtons(tr, this);
    
    return(tr);
  }
  
  @override
  h.Element getHTMLContentsNode() {
    return(h.document.getElementById("content_$id"));
  }
  
  void changeElementValue() {
    String value = control.getValue();
    UndoableEdit edit = new UndoableEdit.compound(Strings.get('form.text_edition'));
    if (firstChild is DNText)
      edit.addSubEdit(new UndoableEdit.removeNode(firstChild, updateDisplay: false));
    if (value != '')
      edit.addSubEdit(new UndoableEdit.insertNode(new Position(this, 0), new ParentUpdatingDNText(value), updateDisplay: false));
    doc.doNewEdit(edit);
    updateButtons();
  }
  
  @override
  void updateHTMLAfterChildrenChange(List<DaxeNode> changed) {
    updateHTML();
  }
  
  void updateButtons() {
    if (parent is DNForm) {
      // update plus/minus buttons
      h.TableRowElement tr = getHTMLNode();
      tr.nodes.last.remove();
      (parent as DNForm).addPlusMinusButtons(tr, this);
    }
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_hidden_div.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_hidden_div.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of nodes;

/**
 * Invisible block with CSS style.
 * Jaxe display type: 'hiddendiv'.
 */
class DNHiddenDiv extends DaxeNode {
  String _styleAtt;
  
  DNHiddenDiv.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
    _styleAtt = doc.cfg.elementParameterValue(ref, 'styleAtt', 'style');
  }
  
  DNHiddenDiv.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
    _styleAtt = doc.cfg.elementParameterValue(ref, 'styleAtt', 'style');
    fixLineBreaks();
  }
  
  @override
  h.Element html() {
    h.DivElement div = new h.DivElement();
    div.id = "$id";
    div.classes.add('dn');
    if (!valid)
      div.classes.add('invalid');
    if (css != null)
      div.setAttribute('style', css);
    // FIXME: align:justify is incompatible with whitespace:pre-wrap
    for (DaxeNode dn = firstChild; dn != null; ) {
      div.append(dn.html());
      dn = dn.nextSibling;
    }
    if (lastChild == null || lastChild.nodeType == DaxeNode.TEXT_NODE)
      div.appendText('\n');
    //this kind of conditional HTML makes it hard to optimize display updates:
    //we have to override updateHTMLAfterChildrenChange
    // also, it seems that in IE this adds a BR instead of a text node !
    return(div);
  }
  
  @override
  h.Element getHTMLContentsNode() {
    return(getHTMLNode());
  }
  
  @override
  void updateHTMLAfterChildrenChange(List<DaxeNode> changed) {
    super.updateHTMLAfterChildrenChange(changed);
    h.DivElement contents = getHTMLContentsNode();
    if (contents.nodes.length > 0) {
      h.Node hn = contents.nodes.first;
      while (hn != null) {
        h.Node next = hn.nextNode;
        if (hn is h.Text || hn is h.BRElement)
          hn.remove();
        hn = next;
      }
    }
    if (lastChild == null || lastChild.nodeType == DaxeNode.TEXT_NODE)
      contents.appendText('\n');
  }
  
  @override
  bool newlineAfter() {
    return(true);
  }
  
  @override
  bool get noDelimiter {
    return(true);
  }
  
  void set css(String value) {
    setAttribute(_styleAtt, value);
  }
  
  String get css {
    return(getAttribute(_styleAtt));
  }
  
  /**
   * Remove the div without removing its content.
   */
  void removeDiv() {
    UndoableEdit edit = new UndoableEdit.compound(Strings.get('undo.remove_element'));
    DaxeNode content = doc.cloneBetween(new Position(this, 0), new Position(this, offsetLength));
    if (doc.hiddenParaRefs != null) {
      // add or remove hidden paragraphs where necessary
      DNHiddenP.fixFragment(parent, content);
    }
    edit.addSubEdit(new UndoableEdit.removeNode(this));
    edit.addSubEdit(doc.insertChildrenEdit(content, new Position(parent, parent.offsetOf(this)), checkValidity:false));
    doc.doNewEdit(edit);
  }
  
  static void removeStyleFromSelection(String cssName) {
    List<DNHiddenDiv> list = divsInSelection();
    if (list.length == 0)
      return;
    UndoableEdit compound = new UndoableEdit.compound(Strings.get('style.remove_styles'));
    for (DNHiddenDiv dn in list) {
      UndoableEdit edit = _removeStyleFromNodeEdit(dn, cssName);
      if (edit != null)
        compound.addSubEdit(edit);
    }
    doc.doNewEdit(compound);
    page.cursor.refresh();
  }
  
  static UndoableEdit _removeStyleFromNodeEdit(DNHiddenDiv dn, String cssName) {
    if (dn.css == null)
      return(null);
    CSSMap cssMap = new CSSMap(dn.css);
    if (cssMap[cssName] != null) {
      cssMap.remove(cssName);
      String newCss = cssMap.toString();
      DaxeAttr att = dn.getAttributeNode(dn._styleAtt);
      att.value = newCss;
      return(new UndoableEdit.changeAttributes(dn, [att], updateDisplay:true));
    }
    return(null);
  }
  
  static void applyStyleToSelection(String cssName, String cssValue) {
    List<DNHiddenDiv> list = divsInSelection();
    if (list.length == 0)
      return;
    UndoableEdit compound = new UndoableEdit.compound(Strings.get('style.apply_style'));
    for (DNHiddenDiv dn in list) {
      UndoableEdit edit = _applyStyleOnNodeEdit(dn, cssName, cssValue);
      if (edit != null)
        compound.addSubEdit(edit);
    }
    doc.doNewEdit(compound);
    page.cursor.refresh();
  }
  
  static UndoableEdit _applyStyleOnNodeEdit(DNHiddenDiv dn, String cssName, String cssValue) {
    CSSMap cssMap = new CSSMap(dn.css);
    cssMap[cssName] = cssValue;
    String newCss = cssMap.toString();
    DaxeAttr att = dn.getAttributeNode(dn._styleAtt);
    if (att == null)
      att = new DaxeAttr(dn._styleAtt, newCss);
    else
      att.value = newCss;
    return(new UndoableEdit.changeAttributes(dn, [att], updateDisplay:true));
  }
  
  static List<DNHiddenDiv> divsInSelection() {
    List<DNHiddenDiv> list = new List<DNHiddenDiv>();
    Position start = page.getSelectionStart();
    Position end = page.getSelectionEnd();
    DaxeNode parent = start.dn;
    while (parent != null && parent is! DNHiddenDiv)
      parent = parent.parent;
    DaxeNode current;
    if (parent is DNHiddenDiv)
      current = parent;
    else if (start.dn is DNText)
      current = start.dn.parent;
    else if (start.dnOffset < start.dn.offsetLength)
      current = start.dn.childAtOffset(start.dnOffset);
    else if (start.dn.offsetLength == 0)
      current = start.dn;
    else // start.dnOffset == start.dn.offsetLength > 0
      current = start.dn.lastChild.nextNode();
    if (current == null)
      return(list);
    if (current is DNHiddenDiv)
      list.add(current);
    // using DaxeNode.nextNode() to iterate through the nodes between start and end
    if (current.parent == null)
      return(list);
    Position nextPos = new Position(current.parent, current.parent.offsetOf(current)+1);
    while (nextPos < end) {
      current = current.nextNode();
      if (current == null)
        break;
      if (current is DNHiddenDiv)
        list.add(current);
      nextPos = new Position(current.parent, current.parent.offsetOf(current)+1);
    }
    return(list);
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_hidden_p.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_hidden_p.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of nodes;

/**
 * Invisible paragraph, inserted automatically when a newline is added
 * at a place where the element is allowed.
 * It must be able to contain text.
 * Jaxe display type: 'hiddenp'.
 */
class DNHiddenP extends DaxeNode {
  String _styleAtt;
  
  DNHiddenP.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
    _styleAtt = doc.cfg.elementParameterValue(ref, 'styleAtt', 'style');
  }
  
  DNHiddenP.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
    _styleAtt = doc.cfg.elementParameterValue(ref, 'styleAtt', 'style');
  }
  
  @override
  h.Element html() {
    h.DivElement div = new h.DivElement();
    div.id = "$id";
    div.classes.add('dn');
    div.classes.add('hiddenp');
    if (!valid)
      div.classes.add('invalid');
    if (css != null)
      div.setAttribute('style', css);
    // FIXME: align:justify is incompatible with whitespace:pre-wrap
    for (DaxeNode dn = firstChild; dn != null; ) {
      div.append(dn.html());
      dn = dn.nextSibling;
    }
    return(div);
  }
  
  @override
  h.Element getHTMLContentsNode() {
    return(getHTMLNode());
  }
  
  @override
  bool get noDelimiter {
    return(true);
  }
  
  @override
  bool newlineAfter() {
    return(true);
  }
  
  void set css(String value) {
    setAttribute(_styleAtt, value);
  }
  
  String get css {
    return(getAttribute(_styleAtt));
  }
  
  bool matchesCss(String cssName, String cssValue) {
    if (css == null)
      return(false);
    CSSMap cssMap = new CSSMap(css);
    return(cssMap[cssName] == cssValue);
  }
  
  static void removeStyleFromSelection(String cssName) {
    List<DNHiddenP> list = paragraphsInSelection();
    if (list.length == 0)
      return;
    UndoableEdit compound = new UndoableEdit.compound(Strings.get('style.remove_styles'));
    for (DNHiddenP p in list) {
      UndoableEdit edit = _removeStyleFromNodeEdit(p, cssName);
      if (edit != null)
        compound.addSubEdit(edit);
    }
    doc.doNewEdit(compound);
    page.cursor.refresh();
  }
  
  static UndoableEdit _removeStyleFromNodeEdit(DNHiddenP p, String cssName) {
    String pcss = p.css;
    if (pcss == null)
      return(null);
    CSSMap cssMap = new CSSMap(p.css);
    if (cssMap[cssName] != null) {
      cssMap.remove(cssName);
      String newCss = cssMap.toString();
      DaxeAttr att = p.getAttributeNode(p._styleAtt);
      att.value = newCss;
      return(new UndoableEdit.changeAttributes(p, [att], updateDisplay:true));
    }
    return(null);
  }
  
  static void applyStyleToSelection(String cssName, String cssValue) {
    List<DNHiddenP> list = paragraphsInSelection();
    if (list.length == 0)
      return;
    UndoableEdit compound = new UndoableEdit.compound(Strings.get('style.apply_style'));
    for (DNHiddenP p in list) {
      UndoableEdit edit = _applyStyleOnNodeEdit(p, cssName, cssValue);
      if (edit != null)
        compound.addSubEdit(edit);
    }
    doc.doNewEdit(compound);
    page.cursor.refresh();
  }
  
  static UndoableEdit _applyStyleOnNodeEdit(DNHiddenP p, String cssName, String cssValue) {
    CSSMap cssMap = new CSSMap(p.css);
    cssMap[cssName] = cssValue;
    String newCss = cssMap.toString();
    DaxeAttr att = p.getAttributeNode(p._styleAtt);
    if (att == null)
      att = new DaxeAttr(p._styleAtt, newCss);
    else
      att.value = newCss;
    return(new UndoableEdit.changeAttributes(p, [att], updateDisplay:true));
  }
  
  static List<DNHiddenP> paragraphsInSelection() {
    List<DNHiddenP> list = new List<DNHiddenP>();
    Position start = page.getSelectionStart();
    Position end = page.getSelectionEnd();
    DaxeNode parent = start.dn;
    while (parent != null && parent is! DNHiddenP)
      parent = parent.parent;
    DaxeNode current;
    if (parent is DNHiddenP)
      current = parent;
    else if (start.dn is DNText)
      current = start.dn.parent;
    else if (start.dnOffset < start.dn.offsetLength)
      current = start.dn.childAtOffset(start.dnOffset);
    else if (start.dn.offsetLength == 0)
      current = start.dn;
    else // start.dnOffset == start.dn.offsetLength > 0
      current = start.dn.lastChild.nextNode();
    if (current == null)
      return(list);
    if (current is DNHiddenP)
      list.add(current);
    // using DaxeNode.nextNode() to iterate through the nodes between start and end
    if (current.parent == null)
      return(list);
    Position nextPos = new Position(current.parent, current.parent.offsetOf(current)+1);
    while (nextPos < end) {
      current = current.nextNode();
      if (current == null)
        break;
      if (current is DNHiddenP)
        list.add(current);
      nextPos = new Position(current.parent, current.parent.offsetOf(current)+1);
    }
    return(list);
  }
  
  /**
   * Called by DaxeDocument when a newline character is inserted.
   * Creates additional paragraphs as necessary.
   * Returns true if the event was handled.
   */
  static bool handleNewlineOnSelection() {
    Position selectionStart = page.getSelectionStart();
    Position selectionEnd = page.getSelectionEnd();
    // note: it is assumed that DNStyle and DNText are allowed in DNHiddenP and
    // DNHiddenP is not allowed in DNStyle.
    // paragraph cuts are only done inside text or style nodes
    DaxeNode textParent = selectionStart.dn;
    int offset = selectionStart.dnOffset;
    while (textParent is DNText || textParent is DNStyle) {
      offset = textParent.parent.offsetOf(textParent);
      textParent = textParent.parent;
    }
    if (textParent is DNHiddenP) {
      // We are in a paragraph, and will need to create another one after the current one,
      // with the fragment to the right of the cursor inside the paragraph.
      UndoableEdit edit = new UndoableEdit.compound(Strings.get('undo.insert_text'));
      DNHiddenP p = textParent;
      Position ppos = new Position(p.parent, p.parent.offsetOf(p));
      Position pstart = new Position(p, 0);
      Position pend = new Position(p, p.offsetLength);
      assert(selectionEnd <= pend); // the selection should not cut the paragraph
      DaxeNode cloneLeft = doc.cloneCutBetween(p, pstart, selectionStart);
      DaxeNode cloneRight = doc.cloneCutBetween(p, selectionEnd, pend);
      edit.addSubEdit(new UndoableEdit.removeNode(p));
      edit.addSubEdit(new UndoableEdit.insertNode(ppos, cloneRight));
      edit.addSubEdit(new UndoableEdit.insertNode(ppos, cloneLeft));
      doc.doNewEdit(edit);
      page.moveCursorTo(new Position(cloneRight, 0));
      page.updateAfterPathChange();
      return(true);
    }
    if (selectionStart.dn != selectionEnd.dn)
      return(false);
    if (textParent.ref == null)
      return(false);
    if (textParent is DNHiddenP)
      return(false);
    x.Element hiddenp = doc.cfg.findSubElement(textParent.ref, doc.hiddenParaRefs);
    if (hiddenp == null)
      return(false);
    if (!doc.cfg.insertIsPossible(textParent, offset, offset, hiddenp))
      return(false);
    
    // There is no paragraph, we need to create one or two.
    // Enclose all possible text and markup to the left of selectionStart
    // in a new hidden paragraph,
    // and create another one afterwards with what can be moved inside
    // from the right of selectionStart.
    UndoableEdit edit = new UndoableEdit.compound(Strings.get('undo.insert_text'));
    
    // clone the nodes that will be in the first paragraph
    int startOffset = offset;
    while (startOffset > 0) {
      DaxeNode child = textParent.childAtOffset(startOffset-1);
      if (child is! DNText && !doc.cfg.isSubElement(hiddenp, child.ref))
        break;
      startOffset--;
    }
    Position pStart = new Position(textParent, startOffset);
    DaxeNode cloneLeft = null;
    if (pStart < selectionStart)
      cloneLeft = doc.cloneCutBetween(textParent, pStart, selectionStart);
    
    // clone the nodes that will be in the second paragraph
    DaxeNode endParent = selectionEnd.dn;
    int endOffset = selectionEnd.dnOffset;
    while (endParent is DNText || endParent is DNStyle) {
      endOffset = endParent.parent.offsetOf(endParent) + 1;
      endParent = endParent.parent;
    }
    assert(endParent == textParent);
    while (endOffset < textParent.offsetLength) {
      DaxeNode child = textParent.childAtOffset(endOffset);
      if (child is! DNText && !doc.cfg.isSubElement(hiddenp, child.ref))
        break;
      endOffset++;
    }
    Position pEnd = new Position(textParent, endOffset);
    DaxeNode cloneRight = null;
    if (pEnd > selectionEnd) {
      cloneRight = doc.cloneCutBetween(textParent, selectionEnd, pEnd);
      // remove possible \n at the end
      DaxeNode lastChild = cloneRight.childAtOffset(cloneRight.offsetLength-1); 
      if (lastChild is DNText) {
        String text = lastChild.nodeValue;
        if (text.length > 0 && text[text.length-1] == '\n') {
          if (text.length == 1)
            cloneRight.removeChild(lastChild);
          else
            lastChild.nodeValue = text.substring(0, text.length - 1);
        }
      }
    }
    
    if (pStart < pEnd) {
      // remove everything moved and the selection
      edit.addSubEdit(doc.removeBetweenEdit(pStart, pEnd));
    }
    
    int p2offset = startOffset;
    
    // create the first paragraph if necessary
    if (cloneLeft != null || textParent.offsetLength == 0) {
      DNHiddenP p1 = NodeFactory.create(hiddenp);
      edit.addSubEdit(new UndoableEdit.insertNode(pStart, p1));
      if (cloneLeft != null)
        edit.addSubEdit(doc.insertChildrenEdit(cloneLeft, new Position(p1, 0)));
      p2offset++;
    }
    
    // create the second paragraph
    DNHiddenP p2 = NodeFactory.create(hiddenp);
    edit.addSubEdit(new UndoableEdit.insertNode(
        new Position(textParent, p2offset), p2));
    if (cloneRight != null)
      edit.addSubEdit(doc.insertChildrenEdit(cloneRight, new Position(p2, 0)));
    
    doc.doNewEdit(edit);
    page.moveCursorTo(new Position(p2, 0));
    page.updateAfterPathChange();
    return(true);
  }
  
  /**
   * Adjust hidden paragraphs in fragment before insert
   */
  static void fixFragment(DaxeNode parent, DaxeNode fragment) {
    if (doc.hiddenParaRefs == null)
      return;
    // do not put a hidden paragraph where it is not allowed (remove one level)
    DaxeNode next;
    for (DaxeNode dn2=fragment.firstChild; dn2 != null; dn2=next) {
      next = dn2.nextSibling;
      if (dn2 is DNHiddenP && !doc.cfg.isSubElement(parent.ref, dn2.ref)) {
        DaxeNode next2;
        for (DaxeNode dn3=dn2.firstChild; dn3 != null; dn3=next2) {
          next2 = dn3.nextSibling;
          dn2.removeChild(dn3);
          fragment.insertBefore(dn3, dn2);
        }
        fragment.removeChild(dn2);
      }
    }
    x.Element hiddenp;
    if (parent.ref != null)
      hiddenp = doc.cfg.findSubElement(parent.ref, doc.hiddenParaRefs);
    else
      hiddenp = null;
    if (hiddenp != null) {
      // add hidden paragraphs if necessary
      for (DaxeNode dn2=fragment.firstChild; dn2 != null; dn2=next) {
        next = dn2.nextSibling;
        if (dn2.ref != hiddenp &&
            ((dn2 is DNText && !doc.cfg.canContainText(parent.ref)) ||
            (!doc.cfg.isSubElement(parent.ref, dn2.ref) &&
                doc.cfg.isSubElement(hiddenp, dn2.ref)))) {
          fragment.removeChild(dn2);
          if (dn2.previousSibling != null && dn2.previousSibling.ref == hiddenp) {
            dn2.previousSibling.appendChild(dn2);
          } else {
            DNHiddenP p = new DNHiddenP.fromRef(hiddenp);
            p.appendChild(dn2);
            fragment.insertBefore(p, next);
          }
        }
      }
    }
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_hr.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_hr.dart
/*
  This file is part of LONCAPA-Daxe.

  LONCAPA-Daxe 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 3 of the License, or
  (at your option) any later version.

  LONCAPA-Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of nodes;

/**
 * HTML horizontal rule
 * Jaxe display type: 'hr'.
 */
class DNHr extends DaxeNode {
  
  DNHr.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
  }
  
  DNHr.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
  }
  
  @override
  h.Element html() {
    h.DivElement div = new h.DivElement();
    div.id = "$id";
    div.classes.add('dn');
    div.classes.add('hr');
    List<x.Element> attRefs = doc.cfg.elementAttributes(ref);
    if (attRefs != null && attRefs.length > 0) {
      div.onClick.listen((h.MouseEvent event) => attributeDialog());
    }
    div.append(new h.HRElement());
    return(div);
  }
  
  @override
  Position firstCursorPositionInside() {
    return(null);
  }
  
  @override
  Position lastCursorPositionInside() {
    return(null);
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_item.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_item.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of nodes;

/**
 * A list element displayed inside a start and end bullets.
 * Jaxe display type: 'item'.
 */
class DNItem extends DaxeNode {
  
  DNItem.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
  }
  
  DNItem.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
    fixLineBreaks();
  }
  
  @override
  h.Element html() {
    h.LIElement li = new h.LIElement();
    li.id = "$id";
    li.classes.add('dn');
    h.ImageElement bullet1 = new h.ImageElement(src: 'packages/daxe/images/bullet1.png', width: 13, height: 13);
    bullet1.classes.add('bullet');
    List<x.Element> attRefs = doc.cfg.elementAttributes(ref);
    if (attRefs != null && attRefs.length > 0)
      bullet1.onClick.listen((h.MouseEvent event) => attributeDialog());
    else {
      bullet1.onDoubleClick.listen((h.MouseEvent event) {
        page.selectNode(this);
        event.preventDefault();
        event.stopPropagation();
      });
    }
    li.append(bullet1);
    h.SpanElement contents = new h.SpanElement();
    DaxeNode dn = firstChild;
    while (dn != null) {
      contents.append(dn.html());
      dn = dn.nextSibling;
    }
    li.append(contents);
    h.ImageElement bullet2 = new h.ImageElement(src: 'packages/daxe/images/bullet2.png', width: 13, height: 13);
    bullet2.classes.add('bullet');
    if (attRefs != null && attRefs.length > 0)
      bullet2.onClick.listen((h.MouseEvent event) => attributeDialog());
    else {
      bullet2.onDoubleClick.listen((h.MouseEvent event) {
        page.selectNode(this);
        event.preventDefault();
        event.stopPropagation();
      });
    }
    li.append(bullet2);
    return(li);
  }
  
  @override
  h.Element getHTMLContentsNode() {
    return(getHTMLNode().nodes[1]);
  }
  
  @override
  bool newlineAfter() {
    return(true);
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_line_break.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_line_break.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of nodes;

/**
 * WYSIWYG line break element (like HTML's br).
 * Jaxe display type: 'br' .
 */
class DNLineBreak extends DaxeNode {
  Tag _b1;
  
  DNLineBreak.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
  }
  
  DNLineBreak.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
  }
  
  @override
  h.Element html() {
    h.Element span;
    span = new h.SpanElement();
    span.id = "$id";
    span.classes.add('dn');
    span.append(new h.BRElement());
    return(span);
  }
  
  @override
  Position firstCursorPositionInside() {
    return(null);
  }
  
  @override
  Position lastCursorPositionInside() {
    return(null);
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_list.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_list.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of nodes;

// TODO: add parameter typeListe
/**
 * A bulleted list containing item nodes.
 * Jaxe display type: 'liste' (list)
 */
class DNList extends DaxeNode {
  Tag _b1, _b2;
  x.Element _itemref;
  
  DNList.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
    init();
    // this is causing problems with item paste, use DNList.withOneEmptyItem instead
    //if (_itemref != null)
    //  appendChild(new DNItem.fromRef(_itemref));
  }
  
  DNList.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
    init();
    fixLineBreaks();
  }
  
  DNList.withOneEmptyItem(x.Element elementRef) : super.fromRef(elementRef) {
    init();
    if (_itemref != null)
      appendChild(new DNItem.fromRef(_itemref));
  }
  
  void init() {
    _b1 = new Tag(this, Tag.START);
    _b2 = new Tag(this, Tag.END);
    List<x.Element> subElements = doc.cfg.subElements(ref);
    if (subElements.length > 0)
      _itemref = subElements[0];
  }
  
  @override
  h.Element html() {
    h.DivElement div = new h.DivElement();
    div.id = "$id";
    div.classes.add('dn');
    if (!valid)
      div.classes.add('invalid');
    div.append(_b1.html());
    h.UListElement list = new h.UListElement();
    list.classes.add('list');
    DaxeNode dn = firstChild;
    while (dn != null) {
      list.append(dn.html());
      dn = dn.nextSibling;
    }
    div.append(list);
    div.append(_b2.html());
    return(div);
  }
  
  /*@override
  Position firstCursorPositionInside() {
    if (firstChild == null) {
      return(null);
    }
    return(new Position(firstChild, 0));
  }
  
  @override
  Position lastCursorPositionInside() {
    if (lastChild == null) {
      return(null);
    }
    return(new Position(lastChild, lastChild.offsetLength));
  }*/
  
  @override
  h.Element getHTMLContentsNode() {
    return(getHTMLNode().nodes[1]);
  }
  
  @override
  bool newlineAfter() {
    return(true);
  }
  
  @override
  bool newlineInside() {
    return(true);
  }
  
  /**
   * Called when a newline character is inserted within an item and a new item should be created.
   */
  static void newlineInItem(Position selectionStart) {
    // the newline is assumed to be at the end of the item, so it does not need to be split.
    DNItem item;
    if (selectionStart.dn is DNItem)
      item = selectionStart.dn;
    else
      item = selectionStart.dn.parent;
    DNItem newitem = NodeFactory.create(item.ref);
    doc.insertNode(newitem,
        new Position(item.parent, item.parent.offsetOf(item) + 1));
    page.moveCursorTo(new Position(newitem, 0));
    page.updateAfterPathChange();
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_processing_instruction.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_processing_instruction.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of nodes;

/**
 * XML Processing instruction.
 */
class DNProcessingInstruction extends DaxeNode {
  Tag _b1, _b2;
  
  DNProcessingInstruction() : super.fromNodeType(DaxeNode.ELEMENT_NODE) {
    _b1 = new Tag(this, Tag.START);
    _b2 = new Tag(this, Tag.END);
  }
  
  DNProcessingInstruction.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
    _b1 = new Tag(this, Tag.START);
    _b2 = new Tag(this, Tag.END);
  }
  
  @override
  h.Element html() {
    h.SpanElement span = new h.SpanElement();
    span.id = "$id";
    span.classes.add('dn');
    span.append(_b1.html());
    h.SpanElement contents = new h.SpanElement();
    DaxeNode dn = firstChild;
    while (dn != null) {
      contents.append(dn.html());
      dn = dn.nextSibling;
    }
    span.append(contents);
    span.append(_b2.html());
    return(span);
  }
  
  @override
  h.Element getHTMLContentsNode() {
    return(getHTMLNode().nodes[1]);
  }
  
  //TODO: provide a way to create a new PI and to change the target
  
  @override
  x.Node toDOMNode(x.Document domDocument) {
    String data = null;
    if (firstChild != null)
      data = firstChild.nodeValue;
    return(domDocument.createProcessingInstruction(nodeName, data));
  }
}



Index: modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_simple_type.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_simple_type.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of nodes;

/**
 * An element with a WXS simple type, displayed according to its type (checkbox, select or input field).
 * Jaxe display type: 'typesimple' (simple type).
 */
class DNSimpleType extends DaxeNode {
  SimpleTypeControl control;
  
  DNSimpleType.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
  }
  
  DNSimpleType.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent, createChildren: false) {
    // manual children addition in order to use ParentUpdatingDNText instead of DNText
    // whenever the text is changed (for instance with find/replace or undo/redo),
    // DNSimpleType.updateHTML is called in order to update the control
    if (node.childNodes != null) {
      DaxeNode prev = null;
      for (x.Node n in node.childNodes) {
        DaxeNode dn;
        if (n.nodeType == x.Node.TEXT_NODE)
          dn = new ParentUpdatingDNText.fromNode(n, this);
        else
          dn = NodeFactory.createFromNode(n, this);
        if (prev == null)
          firstChild = dn;
        else
          prev.nextSibling = dn;
        prev = dn;
      }
    }
  }
  
  @override
  h.Element html() {
    h.Element span;
    span = new h.SpanElement();
    span.id = "$id";
    span.classes.add('dn');
    span.classes.add('simple_type');
    List<x.Element> attRefs = doc.cfg.elementAttributes(ref);
    if (attRefs != null && attRefs.length > 0) {
      h.ImageElement img = new h.ImageElement(src:'images/attributes.png', width:16, height:15);
      img.onClick.listen((h.MouseEvent event) => attributeDialog());
      span.append(img);
    }
    String title = doc.cfg.elementTitle(ref);
    span.append(new h.Text(title));
    
    String value;
    if (firstChild != null)
      value = firstChild.nodeValue;
    else
      value = '';
    control = new SimpleTypeControl.forElement(ref, value, valueChanged: () => changeValue());
    span.append(control.html());
    
    span.onDoubleClick.listen((h.MouseEvent event) {
      page.selectNode(this);
      event.preventDefault();
      event.stopPropagation();
    });
    return(span);
  }
  
  void changeValue() {
    String value = control.getValue();
    UndoableEdit edit = new UndoableEdit.compound(Strings.get('form.text_edition'));
    /* this was causing problems with undo/redo on DNText (the text was not updated)
       (same problem as DNForm.changeElementValue())
       This is solved by using ParentUpdatingDNText
    if (value != '')
      edit.addSubEdit(new UndoableEdit.insertString(new Position(this, 0), value, updateDisplay: false));
    if (firstChild is DNText) {
      edit.addSubEdit(new UndoableEdit.removeString(new Position(firstChild, value.length),
          firstChild.nodeValue.length, updateDisplay: false));
    }
    */
    //this is using ParentUpdatingDNText nodes instead of DNText to avoid the problem
    if (firstChild is DNText)
      edit.addSubEdit(new UndoableEdit.removeNode(firstChild, updateDisplay: false));
    if (value != '')
      edit.addSubEdit(new UndoableEdit.insertNode(new Position(this, 0), new ParentUpdatingDNText(value), updateDisplay: false));
    doc.doNewEdit(edit);
    control.focus();
  }
  
  @override
  void updateHTML() {
    String value;
    if (firstChild != null)
      value = firstChild.nodeValue;
    else
      value = '';
    if (control.getValue() != value)
      control.setValue(value);
  }
  
  @override
  Position firstCursorPositionInside() {
    return(null);
  }
  
  @override
  Position lastCursorPositionInside() {
    return(null);
  }
}



Index: modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_special.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_special.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of nodes;

/**
 * A symbol using UNICODE characters with a UNICODE font such as STIX.
 * Jaxe display type: 'symbole2'.
 */
class DNSpecial extends DaxeNode {
  
  SpecialDialog _dlg;
  String _character;
  
  
  DNSpecial.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
  }
  
  DNSpecial.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent, createChildren: false) {
    _character = node.firstChild.nodeValue;
  }
  
  @override
  h.Element html() {
    h.Element span = new h.SpanElement();
    span.id = "$id";
    span.classes.add('dn');
    span.classes.add('special');
    span.text = _character;
    return(span);
  }
  
  @override
  void attributeDialog([ActionFunction okfct]) {
    _dlg = new SpecialDialog(_character, okfct:() {
      _character = _dlg.getChar();
      if (okfct != null)
        okfct();
    });
    _dlg.show();
  }
  
  @override
  Position firstCursorPositionInside() {
    return(null);
  }
  
  @override
  Position lastCursorPositionInside() {
    return(null);
  }
  
  @override
  x.Node toDOMNode(x.Document domDocument) {
    x.Element el = domDocument.createElementNS(namespaceURI, nodeName);
    el.appendChild(domDocument.createTextNode(_character));
    return(el);
  }
}


/**
 * Dialog for DNSpecial
 */
class SpecialDialog {
  
  static final List<List<String>> symbols = [
    // uppercase greek
    ["\u0393", "\u0394", "\u0398", "\u039B", "\u039E", "\u03A0", "\u03A3", "\u03A5", "\u03A6", "\u03A7",
     "\u03A8", "\u03A9"],
    // lowercase greek
    ["\u03B1", "\u03B2", "\u03B3", "\u03B4", "\u03B5", "\u03B6", "\u03B7", "\u03B8",
     "\u03B9", "\u03BA", "\u03BB", "\u03BC", "\u03BD", "\u03BE", "\u03BF", "\u03C0", "\u03C1", "\u03C2", "\u03C3",
     "\u03C4", "\u03C5", "\u03C6", "\u03C7", "\u03C8", "\u03C9"],
    // greek symbols
    ["\u03D1", "\u03D5", "\u03D6"],
    // maths
    ["\u00AC", "\u00B1", "\u00D7", "\u2113", "\u2102", "\u2115", "\u211A", "\u211D", "\u2124", "\u212B",
     "\u2190", "\u2192", "\u2194", "\u21D0", "\u21D2", "\u21D4",
     "\u2200", "\u2202", "\u2203", "\u2205", "\u2207", "\u2208", "\u2209", "\u2211",
     "\u221D", "\u221E",
     "\u2227", "\u2228", "\u2229", "\u222A", "\u222B", "\u223C", "\u2248", "\u2260", "\u2261", "\u2264", "\u2265", "\u2282"],
    // uppercase cursive
    ["\u{1D49C}", "\u212C", "\u{1D49E}", "\u{1D49F}", "\u2130", "\u2131", "\u{1D4A2}", "\u210B",
     "\u2110", "\u{1D4A5}", "\u{1D4A6}", "\u2112", "\u2133", "\u{1D4A9}", "\u{1D4AA}",
     "\u{1D4AB}", "\u{1D4AC}", "\u211B", "\u{1D4AE}", "\u{1D4AF}", "\u{1D4B0}",
     "\u{1D4B1}", "\u{1D4B2}", "\u{1D4B3}", "\u{1D4B4}", "\u{1D4B5}"]
  ];
  static final int columns = 13; // number of character per line
  
  String character;
  ActionFunction okfct;
  h.TableCellElement selectedTD;
  
  SpecialDialog(this.character, {this.okfct}) {
    
  }
  
  void show() {
    h.DivElement div1 = new h.DivElement();
    div1.id = 'dlg1';
    div1.classes.add('dlg1');
    h.DivElement div2 = new h.DivElement();
    div2.classes.add('dlg2');
    h.DivElement div3 = new h.DivElement();
    div3.classes.add('dlg3');
    h.FormElement form = new h.FormElement();
    
    int rows = 0;
    for (int i=0; i<symbols.length; i++)
      rows += (symbols[i].length / columns).ceil();
    h.TableElement table = new h.TableElement();
    table.classes.add('special_dlg');
    int x = 0;
    for (int i=0; i<symbols.length; i++) {
      h.TableRowElement tr = new h.TableRowElement();
      for (int j=0; j<symbols[i].length; j++) {
        h.TableCellElement td = new h.TableCellElement();
        td.text = symbols[i][j];
        td.style.textAlign = 'center';
        if (character == symbols[i][j]) {
          selectedTD = td;
          selectedTD.style.border = '1px solid black';
        }
        tr.append(td);
        x++;
        if (x >= columns) {
          x = 0;
          if (j < symbols[i].length - 1) {
            table.append(tr);
            tr = new h.TableRowElement();
          }
        }
      }
      if (x != 0) {
        for (int k=x; k<columns; k++)
          tr.append(new h.TableCellElement());
        x = 0;
      }
      table.append(tr);
    }
    table.onClick.listen((h.MouseEvent event) => select(event.target));
    table.onDoubleClick.listen((h.MouseEvent event) {
      select(event.target);
      if (selectedTD != null)
        ok(null);
    });
    form.append(table);
    
    h.DivElement div_buttons = new h.DivElement();
    div_buttons.classes.add('buttons');
    h.ButtonElement bCancel = new h.ButtonElement();
    bCancel.attributes['type'] = 'button';
    bCancel.appendText(Strings.get("button.Cancel"));
    bCancel.onClick.listen((h.MouseEvent event) => cancel());
    div_buttons.append(bCancel);
    h.ButtonElement bOk = new h.ButtonElement();
    bOk.id = 'special_ok';
    if (character == null)
      bOk.disabled = true;
    bOk.attributes['type'] = 'submit';
    bOk.appendText(Strings.get("button.OK"));
    bOk.onClick.listen((h.MouseEvent event) => ok(event));
    div_buttons.append(bOk);
    form.append(div_buttons);
    
    div3.append(form);
    div2.append(div3);
    div1.append(div2);
    h.document.body.append(div1);
  }
  
  void select(h.EventTarget target) {
    if (target is! h.Node)
      return;
    h.Node n = target as h.Node;
    if (n is h.TableCellElement) {
      if (selectedTD != null)
        selectedTD.style.border = '';
      selectedTD = n;
    } else if (n.parent is h.TableCellElement) {
      if (selectedTD != null)
        selectedTD.style.border = null;
      selectedTD = n.parent;
    } else
      return;
    selectedTD.style.border = '1px solid black';
    character = selectedTD.text;
    h.ButtonElement bOk = h.querySelector('button#special_ok');
    bOk.disabled = false;
  }
  
  void ok(h.MouseEvent event) {
    h.querySelector('div#dlg1').remove();
    if (event != null)
      event.preventDefault();
    if (okfct != null)
      okfct();
  }
  
  void cancel() {
    h.querySelector('div#dlg1').remove();
    page.focusCursor();
  }
  
  String getChar() {
    return(character);
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_string.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_string.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of nodes;

/**
 * A short text area displayed within a start and end tags.
 * Jaxe display type: 'string'.
 * 
 * * parameter: `style`: as in DNStyle
 */
class DNString extends DaxeNode {
  Tag _b1, _b2;
  
  DNString.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
    _b1 = new Tag(this, Tag.START);
    _b2 = new Tag(this, Tag.END);
  }
  
  DNString.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
    _b1 = new Tag(this, Tag.START);
    _b2 = new Tag(this, Tag.END);
  }
  
  @override
  h.Element html() {
    h.SpanElement span = new h.SpanElement();
    span.id = "$id";
    span.classes.add('dn');
    if (!valid)
      span.classes.add('invalid');
    span.append(_b1.html());
    h.SpanElement contents = new h.SpanElement();
    DaxeNode dn = firstChild;
    while (dn != null) {
      contents.append(dn.html());
      dn = dn.nextSibling;
    }
    setStyle(contents);
    span.append(contents);
    span.append(_b2.html());
    return(span);
  }
  
  @override
  h.Element getHTMLContentsNode() {
    return(getHTMLNode().nodes[1]);
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_style.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_style.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of nodes;

/**
 * Style node (changes the style without showing tags).
 * Jaxe display type: 'style'.
 * 
 * parameter: `style`: `GRAS` (bold) | `ITALIQUE` (italic) | `EXPOSANT` (superscript) |
 * `INDICE` (subscript) | `SOULIGNE` (underlined) | `BARRE` (strikethrough) |
 * `PCOULEUR[###,###,###]` (text color) | `FCOULEUR[###,###,###]` (background color)
 * (several styles can be combined with a ';')
 * 
 * parameter: 'police' (font)
 * 
 * parameter: 'taille' (size)
 */
class DNStyle extends DaxeNode {
  
  String _style;
  
  DNStyle.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
    _style = doc.cfg.elementParameterValue(elementRef, 'style', null);
  }
  
  DNStyle.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
    _style = doc.cfg.elementParameterValue(ref, 'style', null);
  }
  
  @override
  h.Element html() {
    h.Element span = new h.SpanElement();
    span.id = "$id";
    span.classes.add('dn');
    if (firstChild != null) { // TODO: test for empty sub-styles
      h.SpanElement contents = new h.SpanElement();
      DaxeNode dn = firstChild;
      while (dn != null) {
        contents.append(dn.html());
        dn = dn.nextSibling;
      }
      setStyle(contents);
      span.append(contents);
    } else {
      // let's make this invisible style visible !
      Tag b1 = new Tag(this, Tag.START);
      Tag b2 = new Tag(this, Tag.END);
      span.append(b1.html());
      h.SpanElement contents = new h.SpanElement();
      span.append(contents);
      span.append(b2.html());
    }
    return(span);
  }
  
  @override
  void updateHTMLAfterChildrenChange(List<DaxeNode> changed) {
    super.updateHTML();
  }
  
  @override
  h.Element getHTMLContentsNode() {
    if (firstChild != null)
      return(getHTMLNode().nodes.first);
    else
      return(getHTMLNode().nodes[1]);
  }
  
  @override
  bool get noDelimiter {
    return(true);
  }
  
  void set css(String css) {
    assert(false);
  }
  
  String get css {
    return(null);
  }
  
  bool matches(DNStyle dn) {
    if (dn is DNStyleSpan)
      return(false);
    return(ref == dn.ref);
  }
  
  bool matchesCss(String cssName, String cssValue) {
    return(false);
  }
  
  bool matchesCssName(String cssName) {
    return(false);
  }
  
  /**
   * Removes all styles or one style from the current selection
   */
  static EditAndNewPositions removeStylesBetweenEdit(Position start, Position end,
                                               [x.Element styleRef, String cssName]) {
    start = new Position.clone(start);
    end = new Position.clone(end);
    page.cursor.deSelect();
    start.moveInsideTextNodeIfPossible();
    end.moveInsideTextNodeIfPossible();
    assert(start != end);
    UndoableEdit compound = new UndoableEdit.compound(Strings.get('style.remove_styles'));
    // NOTE: even though selections cannot cut elements, there can be selections like <b>1[<i>23</i>45]6</b>
    DaxeNode ancestor = _commonAncestor(start.dn, end.dn);
    DaxeNode pp = ancestor.parent;
    while (pp != null) {
      if (pp is DNStyle && (styleRef == null || _matchingCssName(pp, styleRef, cssName)))
        ancestor = pp;
      pp = pp.parent;
    }
    if (ancestor is DNText) {
      // nothing to do !
      Position newStart = new Position.leftOffsetPosition(start);
      Position newEnd = new Position.rightOffsetPosition(end);
      EditAndNewPositions ep = new EditAndNewPositions(compound, newStart, newEnd);
      return(ep);
    }
    if (ancestor is DNStyle)
      ancestor = ancestor.parent;
    // ancestor is now a common ancestor between start and end, it is not a style and it has no matching style ancestor
    Position p1 = new Position(ancestor, 0); // left of the ancestor
    Position p2 = new Position(ancestor, ancestor.offsetLength); // right of the ancestor
    DaxeNode before;
    if (p1 != start)
      before = doc.cloneCutBetween(ancestor, p1, start); // clone tree before selection
    else
      before = null;
    DaxeNode inside = doc.cloneCutBetween(ancestor, start, end); // clone tree inside selection
    DaxeNode after;
    if (p2 != end)
      after = doc.cloneCutBetween(ancestor, end, p2); // clone tree after selection
    else
      after = null;
    if (styleRef == null)
      _removeStylesInNode(inside);
    else
      _removeStyleInNode(inside, styleRef, cssName:cssName);
    // calculate start and end positions after the edit
    int p1LeftOffset = new Position.leftOffsetPosition(p1).leftOffset;
    int beforeOffsets;
    if (before != null) {
      beforeOffsets = _offsetsInsideNode(before);
      if (before.lastChild is DNText && inside.firstChild is DNText) {
        // the text nodes will be merged, the position should be inside the text node
        beforeOffsets--;
      }
    } else
      beforeOffsets = 0;
    Position newStart = new Position.fromLeft(p1LeftOffset + beforeOffsets);
    int p2RightOffset = new Position.rightOffsetPosition(p2).rightOffset;
    int afterOffsets;
    if (after != null) {
      afterOffsets = _offsetsInsideNode(after);
      if (after.firstChild is DNText && inside.lastChild is DNText) {
        afterOffsets--;
      }
    } else
      afterOffsets = 0;
    Position newEnd = new Position.fromRight(p2RightOffset + afterOffsets);
    compound.addSubEdit(doc.removeBetweenEdit(p1, p2)); // remove everything inside ancestor
    if (after != null)
      compound.addSubEdit(doc.insertChildrenEdit(after, p1, checkValidity: false)); // insert right tree at p1
    compound.addSubEdit(doc.insertChildrenEdit(inside, p1, checkValidity: false)); // insert style-less tree at p1
    if (before != null)
      compound.addSubEdit(doc.insertChildrenEdit(before, p1, checkValidity: false)); // insert left tree at p1
    EditAndNewPositions ep = new EditAndNewPositions(compound, newStart, newEnd);
    return(ep);
  }
  
  /**
   * Returns the two nodes' first common ancestor.
   */
  static DaxeNode _commonAncestor(DaxeNode dn1, DaxeNode dn2) {
    DaxeNode p1 = dn1;
    while (p1 != null) {
      DaxeNode p2 = dn2;
      while (p2 != null) {
        if (p1 == p2) {
          return(p1);
        }
        if (p2.parent == null)
          break;
        p2 = p2.parent;
      }
      if (p1.parent == null)
        break;
      p1 = p1.parent;
    }
    return(null);
  }
  
  /**
   * Removes all style descendants from the parent, keeping their contents
   * (no validity check).
   */
  static _removeStylesInNode(DaxeNode parent) {
    for (DaxeNode dn = parent.firstChild; dn != null; ) {
      if (dn is DNStyle) {
        DaxeNode first = dn.firstChild;
        for (DaxeNode dn2 = dn.firstChild; dn2 != null; ) {
          DaxeNode next = dn2.nextSibling;
          parent.insertBefore(dn2, dn);
          dn2 = next;
        }
        parent.removeChild(dn);
        dn = first;
      } else {
        _removeStylesInNode(dn);
        dn = dn.nextSibling;
      }
    }
    parent.normalize();
  }
  
  /**
   * Removes all style descendants of a style from the parent, keeping their contents
   * (no validity check).
   */
  static _removeStyleInNode(DaxeNode parent, x.Element styleRef, {String cssName, String cssValue}) {
    for (DaxeNode dn = parent.firstChild; dn != null; ) {
      if ((cssValue != null && _matchingCss(dn, styleRef, cssName, cssValue)) ||
          _matchingCssName(dn, styleRef, cssName)) {
        if (dn is DNStyleSpan && cssName != null && (new CSSMap((dn as DNStyleSpan).css)).keys.length > 1) {
          // actually, in this case we can just remove a part of the style attribute
          CSSMap map = new CSSMap((dn as DNStyleSpan).css);
          map.remove(cssName);
          (dn as DNStyleSpan).css = map.toString();
        } else {
          DaxeNode first = dn.firstChild;
          for (DaxeNode dn2 = dn.firstChild; dn2 != null; ) {
            DaxeNode next = dn2.nextSibling;
            parent.insertBefore(dn2, dn);
            dn2 = next;
          }
          parent.removeChild(dn);
          dn = first;
        }
      } else {
        _removeStyleInNode(dn, styleRef, cssName:cssName, cssValue:cssValue);
        dn = dn.nextSibling;
      }
    }
    parent.normalize();
  }
  
  static int _offsetsInsideNode(DaxeNode parent) {
    DaxeNode targetDn = parent;
    int targetDnOffset = parent.offsetLength;
    int n = 0;
    DaxeNode dn = parent;
    int dnOffset = 0;
    while (dn != targetDn || dnOffset != targetDnOffset) {
      if (dnOffset == dn.offsetLength) {
        dnOffset = dn.parent.offsetOf(dn) + 1;
        dn = dn.parent;
      } else if (dn is DNText) {
        dnOffset++;
      } else {
        dn = dn.childAtOffset(dnOffset);
        dnOffset = 0;
      }
      n++;
    }
    return(n);
  }
  
  /**
   * Edit to remove the first matching style for the node at given Position
   */
  static UndoableEdit removeStyleAtPositionEdit(Position pos, [x.Element styleRef, String cssName]) {
    DaxeNode parent = pos.dn;
    if (parent is DNText)
      parent = parent.parent;
    for (DaxeNode dn = parent; dn != null; dn = dn.parent) {
      if ((styleRef == null && dn is DNStyle) || _matchingCssName(dn, styleRef, cssName)) {
        if (dn is DNStyleSpan && cssName != null && (new CSSMap((dn as DNStyleSpan).css)).keys.length > 1) {
          // actually, in this case we can just remove a part of the style attribute
          CSSMap map = new CSSMap((dn as DNStyleSpan).css);
          map.remove(cssName);
          DaxeAttr attr = new DaxeAttr((dn as DNStyleSpan)._styleAtt, map.toString());
          return(new UndoableEdit.changeAttribute(dn, attr));
        } else {
          UndoableEdit edit = new UndoableEdit.compound(Strings.get('undo.remove_element'));
          DaxeNode clone = new DaxeNode.clone(dn);
          Position after = new Position(dn.parent, dn.parent.offsetOf(dn)+1);
          edit.addSubEdit(doc.insertChildrenEdit(clone, after, checkValidity:false));
          edit.addSubEdit(new UndoableEdit.removeNode(dn));
          return(edit);
        }
      }
    }
    return(null);
  }
  
  /**
   * Removes the given style from the current selection. If the selection is empty,
   * removes the style from all ancestors of the node at the cursor position.
   */
  static void removeStylesFromSelection([x.Element styleRef, String cssName]) {
    Position start = page.getSelectionStart();
    Position end = page.getSelectionEnd();
    if (start == null)
      return;
    if (start == end) {
      UndoableEdit edit = removeStyleAtPositionEdit(start, styleRef, cssName);
      if (edit != null)
        doc.doNewEdit(edit);
      //TODO: restore position
      page.updateAfterPathChange();
    } else {
      // remove the style only in the selection
      EditAndNewPositions ep = removeStylesBetweenEdit(start, end, styleRef, cssName);
      doc.doNewEdit(ep.edit);
      page.cursor.setSelection(ep.start, ep.end);
      page.updateAfterPathChange();
    }
  }
  
  static bool _matchingCssName(DaxeNode dn, x.Element styleRef, String cssName) {
    if (dn is! DNStyle)
      return(false);
    if (dn.ref != styleRef)
      return(false);
    if (cssName == null)
      return(true);
    if ((dn as DNStyle).css == null)
      return(false);
    return((dn as DNStyle).matchesCssName(cssName));
  }
  
  static bool _matchingCss(DaxeNode dn, x.Element styleRef, String cssName, String cssValue) {
    if (dn is! DNStyle)
      return(false);
    if (dn.ref != styleRef)
      return(false);
    if ((dn as DNStyle).css == null)
      return(true);
    return((dn as DNStyle).matchesCss(cssName, cssValue));
  }
  
  /**
   * Edit to create a new node at Position
   */
  /* unused
  static UndoableEdit newStyleNodeEdit(Position pos, x.Element styleRef, [String css]) {
    DaxeNode styleNode = NodeFactory.create(styleRef, 'element');
    if (css != null)
      (styleNode as DNStyle).css = css;
    return(new UndoableEdit.insertNode(pos, styleNode));
  }
  */
  
  /**
   * Edit to apply style between 2 positions (start < end)
   */
  static EditAndNewPositions applyStyleBetweenEdit(Position start, Position end,
      x.Element styleRef, {String cssName, String cssValue}) {
    assert(start != end);
    UndoableEdit edit = new UndoableEdit.compound(Strings.get('style.apply_style'));
    // p1 and p2 are used to merge with sibling styles
    // newStart and newEnd are ajusted selection positions after the edit
    Position p1 = start;
    Position newStart = new Position.leftOffsetPosition(start);
    Position testStart = new Position.clone(start);
    testStart.moveInsideTextNodeIfPossible();
    if (testStart.dn is DNText && testStart.dnOffset == 0) {
      DaxeNode previous = testStart.dn.previousSibling; 
      if (previous is DNStyle && _matchingCss(previous, styleRef, cssName, cssValue)) {
        p1 = new Position(previous.parent, previous.parent.offsetOf(previous));
        newStart = new Position.leftOffsetPosition(testStart);
        newStart.move(-2);
        if (previous.lastChild is DNText)
          newStart.move(-1);
      }
    }
    Position p2 = end;
    Position newEnd = new Position.rightOffsetPosition(end);
    Position testEnd = new Position.clone(end);
    testEnd.moveInsideTextNodeIfPossible();
    if (testEnd.dn is DNText && testEnd.dnOffset == testEnd.dn.offsetLength) {
      DaxeNode next = testStart.dn.nextSibling; 
      if (next is DNStyle && _matchingCss(next, styleRef, cssName, cssValue)) {
        p2 = new Position(next.parent, next.parent.offsetOf(next) + 1);
        newEnd = new Position.rightOffsetPosition(testEnd);
        newEnd.move(2);
        if (next.firstChild is DNText)
          newEnd.move(1);
      }
    }
    DaxeNode clone = doc.cloneBetween(p1, p2);
    _removeStyleInNode(clone, styleRef, cssName:cssName, cssValue:cssValue);
    _applyStyleInNode(clone, styleRef, cssName, cssValue);
    edit.addSubEdit(doc.insertChildrenEdit(clone, p2, checkValidity:false));
    edit.addSubEdit(doc.removeBetweenEdit(p1, p2));
    return(new EditAndNewPositions(edit, newStart, newEnd));
  }
  
  /**
   * Apply given style to selection, or create a new node if selection is empty.
   * The style must no be already applied to ancestors of the selection nodes
   * (if it can be, removeAndApplyStyleToSelection should be used instead).
   */
  static void applyStyleInsideSelection(x.Element styleRef, {String cssName, String cssValue}) {
    Position start = page.getSelectionStart();
    Position end = page.getSelectionEnd();
    if (start == end) {
      DaxeNode parent = start.dn;
      if (parent is DNText)
        parent = parent.parent;
      DaxeNode styleNode = NodeFactory.create(styleRef, 'element');
      if (cssName != null && cssValue != null)
        (styleNode as DNStyle).css = "$cssName: $cssValue";
      bool inserted = false;
      if (doc.hiddenParaRefs != null) {
        if (!doc.cfg.isSubElement(parent.ref, styleRef)) {
          x.Element hiddenp = doc.cfg.findSubElement(parent.ref, doc.hiddenParaRefs);
          if (hiddenp != null && doc.cfg.isSubElement(hiddenp, styleRef)) {
            // a new paragraph must be created
            DNHiddenP p = new DNHiddenP.fromRef(hiddenp);
            p.appendChild(styleNode);
            doc.insertNode(p, start);
            inserted = true;
          }
        }
      }
      if (!inserted)
        doc.insertNode(styleNode, start);
      Position cursorPos = styleNode.firstCursorPositionInside();
      if (cursorPos != null) {
        page.moveCursorTo(cursorPos);
        page.updateAfterPathChange();
      }
      return;
    }
    EditAndNewPositions ep = applyStyleBetweenEdit(start, end, styleRef,
        cssName:cssName, cssValue:cssValue);
    doc.doNewEdit(ep.edit);
    page.cursor.setSelection(ep.start, ep.end);
    page.updateAfterPathChange();
  }
  
  /**
   * Apply given style inside given parent wherever it is valid.
   */
  static void _applyStyleInNode(DaxeNode parent, x.Element styleRef, String cssName, String cssValue) {
    if (doc.cfg.isSubElement(parent.ref, styleRef)) {
      DaxeNode styleNode = null;
      for (DaxeNode child=parent.firstChild; child!=null; ) {
        if (child is DNText || doc.cfg.isSubElement(styleRef, child.ref)) {
          DaxeNode next = child.nextSibling;
          parent.removeChild(child);
          if (styleNode == null) {
            styleNode = NodeFactory.create(styleRef, 'element');
            if (cssName != null && cssValue != null)
              (styleNode as DNStyle).css = "$cssName: $cssValue";
          }
          styleNode.appendChild(child);
          child = next;
        } else {
          if (styleNode != null) {
            parent.insertBefore(styleNode, child);
            styleNode = null;
          }
          _applyStyleInNode(child, styleRef, cssName, cssValue);
          child = child.nextSibling;
        }
      }
      if (styleNode != null)
        parent.appendChild(styleNode);
    } else {
      for (DaxeNode child=parent.firstChild; child!=null; child=child.nextSibling) {
        if (child is! DNText)
          _applyStyleInNode(child, styleRef, cssName, cssValue);
      }
    }
  }
  
  /**
   * Remove any style matching cssName in the selection, and apply the css style to it,
   * or create a new style node if the selection is empty.
   * An ancestor of the selection nodes may match cssName.
   */
  static void removeAndApplyStyleToSelection(x.Element styleRef, String cssName, String cssValue) {
    Position start = page.getSelectionStart();
    Position end = page.getSelectionEnd();
    if (start == end) {
      DNStyle.applyStyleInsideSelection(styleRef, cssName:cssName, cssValue:cssValue);
    } else {
      EditAndNewPositions ep1 = DNStyle.removeStylesBetweenEdit(start, end, styleRef, cssName);
      doc.doNewEdit(ep1.edit);
      EditAndNewPositions ep2 = applyStyleBetweenEdit(ep1.start, ep1.end, styleRef,
          cssName:cssName, cssValue:cssValue);
      doc.doNewEdit(ep2.edit);
      doc.combineLastEdits(Strings.get('style.apply_style'), 2);
      page.cursor.setSelection(ep2.start, ep2.end);
      page.updateAfterPathChange();
    }
  }
  
  /**
   * Merge the styles at Position if possible, or returns null.
   */
  static EditAndNewPositions mergeAt(Position pos) {
    DNStyle leftStyle = null, rightStyle = null;
    if (pos.dn is DNStyle && pos.dnOffset == pos.dn.offsetLength)
      leftStyle = pos.dn;
    else if (pos.dn is DNText && pos.dnOffset == pos.dn.offsetLength && pos.dn.parent is DNStyle &&
        pos.dn.parent.offsetOf(pos.dn) == pos.dn.parent.offsetLength)
      leftStyle = pos.dn.parent;
    else if (pos.dn is! DNText && pos.dnOffset > 0 && pos.dn.offsetLength > 0 &&
        pos.dn.childAtOffset(pos.dnOffset - 1) is DNStyle)
      leftStyle = pos.dn.childAtOffset(pos.dnOffset - 1);
    if (pos.dn is DNStyle && pos.dnOffset == 0)
      rightStyle= pos.dn;
    else if (pos.dn is DNText && pos.dnOffset == 0 && pos.dn.parent is DNStyle &&
        pos.dn.parent.offsetOf(pos.dn) == 0)
      rightStyle = pos.dn.parent;
    else if (pos.dn is! DNText && pos.dnOffset < pos.dn.offsetLength && pos.dn.offsetLength > 0 &&
        pos.dn.childAtOffset(pos.dnOffset) is DNStyle)
      rightStyle = pos.dn.childAtOffset(pos.dnOffset);
    if (leftStyle == null || rightStyle == null)
      return(null);
    if (!leftStyle.matches(rightStyle))
      return(null);
    Position newPos;
    if (leftStyle.lastChild is DNText && rightStyle.firstChild is DNText)
      newPos = new Position(leftStyle.lastChild, leftStyle.lastChild.offsetLength);
    else
      newPos = new Position(leftStyle, leftStyle.offsetLength);
    UndoableEdit edit = new UndoableEdit.compound('merge');
    edit.addSubEdit(new UndoableEdit.removeNode(rightStyle));
    DaxeNode clone = new DaxeNode.clone(rightStyle);
    edit.addSubEdit(doc.insertChildrenEdit(clone, new Position(leftStyle, leftStyle.offsetLength)));
    return(new EditAndNewPositions(edit, newPos, new Position.clone(newPos)));
  }
}

class EditAndNewPositions {
  UndoableEdit edit;
  Position start, end;
  
  EditAndNewPositions(this.edit, this.start, this.end);
}

Index: modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_style_span.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_style_span.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of nodes;

/**
 * Invisible style node using a style attribute with CSS.
 * The style can be changed with the toolbar.
 * Jaxe display type: 'stylespan'.
 */
class DNStyleSpan extends DNStyle {
  String _styleAtt;
  
  DNStyleSpan.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
    _styleAtt = doc.cfg.elementParameterValue(ref, 'styleAtt', 'style');
  }
  
  DNStyleSpan.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
    _styleAtt = doc.cfg.elementParameterValue(ref, 'styleAtt', 'style');
  }
  
  @override
  h.Element html() {
    h.Element span = new h.SpanElement();
    span.id = "$id";
    span.classes.add('dn');
    if (firstChild != null) { // TODO: test for empty sub-styles
      h.SpanElement contents = new h.SpanElement();
      DaxeNode dn = firstChild;
      while (dn != null) {
        contents.append(dn.html());
        dn = dn.nextSibling;
      }
      if (css != null)
        contents.setAttribute('style', css);
      span.append(contents);
    } else {
      // let's make this invisible style visible !
      // TODO: use the toolbar instead
      Tag b1 = new Tag(this, Tag.START);
      Tag b2 = new Tag(this, Tag.END);
      span.append(b1.html());
      h.SpanElement contents = new h.SpanElement();
      contents.setAttribute('style', css);
      span.append(contents);
      span.append(b2.html());
    }
    return(span);
  }
  
  void set css(String css) {
    setAttribute(_styleAtt, css);
  }
  
  String get css {
    return(getAttribute(_styleAtt));
  }
  
  String get styleAtt {
    return(_styleAtt);
  }
  
  @override
  void updateHTMLAfterChildrenChange(List<DaxeNode> changed) {
    super.updateHTML();
  }
  
  @override
  h.Element getHTMLContentsNode() {
    if (firstChild != null)
      return(getHTMLNode().nodes.first);
    else
      return(getHTMLNode().nodes[1]);
  }
  
  static x.Element styleSpanRef() {
    return(doc.cfg.firstElementWithType('stylespan'));
  }
  
  @override
  bool matches(DNStyle dn) {
    if (dn is DNStyleSpan) {
      CSSMap cssMap1 = new CSSMap(css);
      CSSMap cssMap2 = new CSSMap(dn.css);
      return(cssMap1.equivalent(cssMap2));
    } else {
      return(false);
    }
  }
  
  @override
  bool matchesCss(String cssName, String cssValue) {
    if (css == null)
      return(false);
    CSSMap cssMap = new CSSMap(css);
    return(cssMap[cssName] == cssValue);
  }
  
  @override
  bool matchesCssName(String cssName) {
    if (css == null)
      return(false);
    CSSMap cssMap = new CSSMap(css);
    return(cssMap[cssName] != null);
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_table.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_table.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of nodes;

/**
 * Table able to contain other elements in its cells.
 * Jaxe display type: 'tabletexte' (text table).
 * 
 * * parameter: `trTag`: the name of row elements
 * * parameter: `tdTag`: the name of cell elements
 * * parameter: `thTag`: the name of header elements
 * * parameter: `tbodyTag`: 'tbody' in HTML (only import is supported)
 * * parameter: `theadTag`: 'thead' in HTML (only import is supported)
 * * parameter: `tfootTag`: 'tfoot' in HTML (only import is supported)
 * * parameter: `colspanAttr`: the name of the colspan attribute
 * * parameter: `rowspanAttr`: the name of the rowspan attribute
 */
class DNTable extends DaxeNode {
  String _trtag, _tdtag, _thtag;
  x.Element _trref, _tdref, _thref;
  String _theadtag, _tbodytag, _tfoottag;
  String _colspanAttr, _rowspanAttr, _alignAttr;
  bool header;
  
  DNTable.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
    init();
    createNewStructure();
  }
  
  DNTable.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent, createChildren: false) {
    init();
    for (x.Node xn=node.firstChild; xn != null; xn=xn.nextSibling) {
      if (xn.nodeType == x.Node.ELEMENT_NODE && xn.nodeName == _theadtag)
        _appendRowsFromDOM(xn);
    }
    for (x.Node xn=node.firstChild; xn != null; xn=xn.nextSibling) {
      if (xn.nodeType == x.Node.ELEMENT_NODE && xn.nodeName == _tbodytag)
        _appendRowsFromDOM(xn);
    }
    _appendRowsFromDOM(node);
    for (x.Node xn=node.firstChild; xn != null; xn=xn.nextSibling) {
      if (xn.nodeType == x.Node.ELEMENT_NODE && xn.nodeName == _tfoottag)
        _appendRowsFromDOM(xn);
    }
    if (firstChild != null && firstChild.firstChild is DNTH)
      header = true;
  }
  
  // appends only the tr/td and tr/th from the node to the table
  void _appendRowsFromDOM(x.Node node) {
    for (x.Node xtr=node.firstChild; xtr != null; xtr=xtr.nextSibling) {
      if (xtr.nodeType == x.Node.ELEMENT_NODE && xtr.nodeName == _trtag) {
        DaxeNode tr = new DNTR.fromNode(xtr, this);
        for (x.Node xtd=xtr.firstChild; xtd != null; xtd=xtd.nextSibling) {
          if (xtd.nodeType == x.Node.ELEMENT_NODE) {
            if (xtd.nodeName == _tdtag) {
              DaxeNode td = new DNTD.fromNode(xtd, tr);
              tr.appendChild(td);
            } else if (xtd.nodeName == _thtag) {
              DaxeNode th = new DNTH.fromNode(xtd, tr);
              tr.appendChild(th);
            }
          }
        }
        appendChild(tr);
      }
    }
  }
  
  void init() {
    userCannotEdit = true;
    _trtag = doc.cfg.elementParameterValue(ref, 'trTag', 'tr');
    List<x.Element> trRefs = doc.cfg.elementReferences(_trtag);
    _trref = doc.cfg.findSubElement(ref, trRefs);
    _tdtag = doc.cfg.elementParameterValue(ref, 'tdTag', 'td');
    List<x.Element> tdRefs = doc.cfg.elementReferences(_tdtag);
    _tdref = doc.cfg.findSubElement(_trref, tdRefs);
    _thtag = doc.cfg.elementParameterValue(ref, 'thTag', 'th');
    List<x.Element> thRefs = doc.cfg.elementReferences(_thtag);
    _thref = doc.cfg.findSubElement(_trref, thRefs);
    _tbodytag = doc.cfg.elementParameterValue(ref, 'tbodyTag', 'tbody');
    _theadtag = doc.cfg.elementParameterValue(ref, 'theadTag', 'thead');
    _tfoottag = doc.cfg.elementParameterValue(ref, 'tfootTag', 'tfoot');
    _colspanAttr = doc.cfg.elementParameterValue(ref, 'colspanAttr', null);
    _rowspanAttr = doc.cfg.elementParameterValue(ref, 'rowspanAttr', null);
    _alignAttr = doc.cfg.elementParameterValue(ref, 'alignAttr', null);
    header = false;
  }
  
  @override
  h.Element html() {
    h.DivElement div = new h.DivElement();
    div.id = "$id";
    div.classes.add('dn');
    div.classes.add('table');
    
    h.FormElement form = new h.FormElement();
    form.classes.add('table_buttons');
    
    h.ButtonElement bTable = new h.ButtonElement();
    bTable.attributes['type'] = 'button';
    bTable.appendText(Strings.get("table.Table"));
    bTable.onClick.listen((h.MouseEvent event) => attributeDialog());
    bTable.style.marginRight = '1em';
    form.append(bTable);
    
    h.ButtonElement bRow = new h.ButtonElement();
    bRow.attributes['type'] = 'button';
    bRow.appendText(Strings.get("table.Row"));
    bRow.onClick.listen((h.MouseEvent event) => rowAttributes());
    bRow.style.marginRight = '0.2em';
    form.append(bRow);
    
    h.ButtonElement bInsertRow = new h.ButtonElement();
    bInsertRow.attributes['type'] = 'button';
    bInsertRow.appendText("+");
    bInsertRow.onClick.listen((h.MouseEvent event) => insertRow());
    bInsertRow.style.marginRight = '0.2em';
    form.append(bInsertRow);
    
    h.ButtonElement bRemoveRow = new h.ButtonElement();
    bRemoveRow.attributes['type'] = 'button';
    bRemoveRow.appendText("-");
    bRemoveRow.onClick.listen((h.MouseEvent event) => removeRow());
    bRemoveRow.style.marginRight = '1em';
    form.append(bRemoveRow);
    
    form.appendText(Strings.get('table.Column'));
    h.ButtonElement bInsertColumn = new h.ButtonElement();
    bInsertColumn.attributes['type'] = 'button';
    bInsertColumn.appendText("+");
    bInsertColumn.onClick.listen((h.MouseEvent event) => insertColumn());
    bInsertColumn.style.marginRight = '0.2em';
    form.append(bInsertColumn);
    
    h.ButtonElement bRemoveColumn = new h.ButtonElement();
    bRemoveColumn.attributes['type'] = 'button';
    bRemoveColumn.appendText("-");
    bRemoveColumn.onClick.listen((h.MouseEvent event) => removeColumn());
    bRemoveColumn.style.marginRight = '1em';
    form.append(bRemoveColumn);
    
    h.ButtonElement bCell = new h.ButtonElement();
    bCell.attributes['type'] = 'button';
    bCell.appendText(Strings.get("table.Cell"));
    bCell.onClick.listen((h.MouseEvent event) => cellAttributes());
    bCell.style.marginRight = '1em';
    form.append(bCell);
    
    h.CheckboxInputElement bheader = new h.CheckboxInputElement();
    bheader.id = "header$id";
    bheader.checked = header;
    bheader.onClick.listen((h.MouseEvent event) => toggleHeader());
    form.append(bheader);
    h.LabelElement headerLabel = new h.LabelElement();
    headerLabel.htmlFor = "header$id";
    headerLabel.appendText(Strings.get('table.header'));
    headerLabel.style.marginRight = '1em';
    form.append(headerLabel);
    
    h.ButtonElement bMergeRight = new h.ButtonElement();
    bMergeRight.attributes['type'] = 'button';
    h.ImageElement img = new h.ImageElement();
    img.src = 'packages/daxe/images/mergeright.png';
    bMergeRight.append(img);
    bMergeRight.onClick.listen((h.MouseEvent event) => mergeRight());
    bMergeRight.style.marginRight = '0.2em';
    bMergeRight.title = Strings.get('table.merge_right');
    form.append(bMergeRight);
    
    h.ButtonElement bSplitX = new h.ButtonElement();
    bSplitX.attributes['type'] = 'button';
    img = new h.ImageElement();
    img.src = 'packages/daxe/images/splitx.png';
    bSplitX.append(img);
    bSplitX.onClick.listen((h.MouseEvent event) => splitX());
    bSplitX.style.marginRight = '0.2em';
    bSplitX.title = Strings.get('table.split_x');
    form.append(bSplitX);
    
    h.ButtonElement bMergeBottom = new h.ButtonElement();
    bMergeBottom.attributes['type'] = 'button';
    img = new h.ImageElement();
    img.src = 'packages/daxe/images/mergebottom.png';
    bMergeBottom.append(img);
    bMergeBottom.onClick.listen((h.MouseEvent event) => mergeBottom());
    bMergeBottom.style.marginRight = '0.2em';
    bMergeBottom.title = Strings.get('table.merge_bottom');
    form.append(bMergeBottom);
    
    h.ButtonElement bSplitY = new h.ButtonElement();
    bSplitY.attributes['type'] = 'button';
    img = new h.ImageElement();
    img.src = 'packages/daxe/images/splity.png';
    bSplitY.append(img);
    bSplitY.onClick.listen((h.MouseEvent event) => splitY());
    bSplitY.style.marginRight = '0.2em';
    bSplitY.title = Strings.get('table.split_y');
    form.append(bSplitY);
    
    div.append(form);
    
    h.TableElement table = new h.TableElement();
    table.classes.add('indent');
    DaxeNode dn = firstChild;
    while (dn != null) {
      table.append(dn.html());
      dn = dn.nextSibling;
    }
    div.append(table);
    return(div);
  }
  
  @override
  Position firstCursorPositionInside() {
    if (firstChild == null || firstChild.firstChild == null) {
      return(null);
    }
    return(new Position(firstChild.firstChild, 0));
  }
  
  @override
  Position lastCursorPositionInside() {
    if (lastChild == null || lastChild.lastChild == null) {
      return(null);
    }
    return(new Position(lastChild.lastChild, lastChild.lastChild.offsetLength));
  }
  
  void createNewStructure() {
    int rows = 2;
    int columns = 2;
    for (int i=0; i<rows; i++) {
      DNTR tr = new DNTR.fromRef(_trref);
      for (int j=0; j<columns; j++) {
        DNTD td = new DNTD.fromRef(_tdref);
        tr.appendChild(td);
      }
      appendChild(tr);
    }
  }
  
  void rowAttributes() {
    DNTR tr = getSelectedRow();
    if (tr == null)
      return;
    tr.attributeDialog();
  }
  
  void insertRow() {
    DNTR selectedRow = getSelectedRow();
    if (selectedRow == null)
      return;
    DaxeNode newRow = new DNTR.fromRef(_trref);
    for (int i=0; i<xLength(); i++) {
      DNTD td = new DNTD.fromRef(_tdref);
      newRow.appendChild(td);
    }
    doc.insertNode(newRow, new Position(this, offsetOf(selectedRow) + 1));
    page.moveCursorTo(newRow.firstCursorPositionInside());
  }
  
  void removeRow() {
    DNTR tr = getSelectedRow();
    if (tr == null)
      return;
    UndoableEdit edit = new UndoableEdit.compound(Strings.get('undo.remove'));
    for (DNTD td in tr.childNodes) {
      if (td.rowspan > 1)
        splitY(td, edit);
    }
    edit.addSubEdit(new UndoableEdit.removeNode(tr));
    doc.doNewEdit(edit);
  }
  
  void insertColumn() {
    DNTD td = getSelectedCell();
    if (td == null)
      return;
    UndoableEdit edit = new UndoableEdit.compound(Strings.get('undo.insert'));
    int x = getCellX(td);
    for (DaxeNode tr in childNodes) {
      DNTD newtd;
      if (firstChild == tr && header)
        newtd = new DNTH.fromRef(_thref);
      else
        newtd = new DNTD.fromRef(_tdref);
      int offset = 0;
      for (DNTD td = tr.firstChild; td != null && x >= getCellX(td); td = td.nextSibling) {
        offset++;
      }
      edit.addSubEdit(new UndoableEdit.insertNode(new Position(tr, offset), newtd));
    }
    doc.doNewEdit(edit);
    page.moveCursorTo(new Position(td.nextSibling, 0));
  }
  
  void removeColumn() {
    DNTD td = getSelectedCell();
    if (td == null)
      return;
    Position futurePos;
    if (td.nextSibling != null)
      futurePos = new Position(td.nextSibling, 0);
    else if (td.previousSibling != null)
      futurePos = new Position(td.previousSibling, 0);
    else
      futurePos = null;
    UndoableEdit edit = new UndoableEdit.compound(Strings.get('undo.remove'));
    int x = getCellX(td);
    int y = 0;
    for (DaxeNode tr in childNodes) {
      DNTD td = getCell(x, y);
      while (td.colspan > 1) {
        splitX(td); // FIXME: should be undoable, but how do we get the reference to the created td ?
        td = getCell(x, y);
      }
      edit.addSubEdit(new UndoableEdit.removeNode(td));
      y++;
    }
    doc.doNewEdit(edit);
    if (futurePos != null)
      page.moveCursorTo(futurePos);
  }
  
  void cellAttributes() {
    DNTD td = getSelectedCell();
    if (td == null)
      return;
    td.attributeDialog();
  }
  
  DNTD getSelectedCell() {
    Position pos = page.getSelectionStart();
    DaxeNode parent = pos.dn;
    while (parent != null && parent is! DNTD)
      parent = parent.parent;
    return(parent);
  }
  
  DNTR getSelectedRow() {
    Position pos = page.getSelectionStart();
    DaxeNode parent = pos.dn;
    while (parent != null && parent is! DNTR)
      parent = parent.parent;
    return(parent);
  }
  
  DNTD getCell(int x, int y) {
    return(grid()[x][y]); // could be optimized with a cache
  }
  
  int getCellX(DNTD td) {
    List<List<DNTD>> g = grid();
    for (int x=0; x<g.length; x++)
      for (int y=0; y<g[0].length; y++)
        if (g[x][y] == td)
          return(x);
    assert(false);
    return(-1);
  }
  
  int getCellY(DNTD td) {
    List<List<DNTD>> g = grid();
    for (int x=0; x<g.length; x++)
      for (int y=0; y<g[0].length; y++)
        if (g[x][y] == td)
          return(y);
    assert(false);
    return(-1);
  }
  
  List<List<DNTD>> grid() {
    int dx = xLength();
    int dy = yLength();
    // to get g[dx][dy]
    List<List<DNTD>> g = new List<List<DNTD>>(dx);
    for (int x=0; x<dx; x++)
      g[x] = new List<DNTD>(dy);
    int y = 0;
    for (DNTR tr in childNodes) {
      int x = 0;
      for (DNTD td in tr.childNodes) {
        while (x < dx && g[x][y] != null)
          x++;
        for (int ix=0; ix < td.colspan; ix++) {
          for (int iy=0; iy < td.rowspan; iy++) {
            g[x+ix][y+iy] = td;
          }
        }
        x += td.colspan;
      }
      y++;
    }
    return(g);
  }
  
  int xLength() {
    int dx = 0;
    for (DNTR tr in childNodes) {
      int n = 0;
      for (DNTD td in tr.childNodes) {
        n += td.colspan;
      }
      dx = max(dx, n);
    }
    return(dx);
  }
  
  int yLength() {
    int dy = 0;
    for (DNTR tr in childNodes) {
      DNTD td = tr.firstChild;
      dy += td.rowspan;
    }
    return(dy);
  }
  
  void toggleHeader() {
    DNTR firstTR = firstChild;
    if (firstTR == null)
      return;
    DaxeNode cell = firstTR.firstChild;
    while (cell != null) {
      DaxeNode nextCell = cell.nextSibling;
      if (header && cell is DNTH) {
        DNTD td = new DNTD.fromRef(_tdref);
        DaxeNode child = cell.firstChild;
        while (child != null) {
          DaxeNode nextChild = child.nextSibling;
          cell.removeChild(child);
          td.appendChild(child);
          child = nextChild;
        }
        for (DaxeAttr attr in cell.attributes)
          td.setAttribute(attr.name, attr.value);
        cell.replaceWith(td);
      } else if (!header && cell is DNTD && cell is! DNTH) {
        DNTH th = new DNTH.fromRef(_thref);
        DaxeNode child = cell.firstChild;
        while (child != null) {
          DaxeNode nextChild = child.nextSibling;
          cell.removeChild(child);
          th.appendChild(child);
          child = nextChild;
        }
        for (DaxeAttr attr in cell.attributes)
          th.setAttribute(attr.name, attr.value);
        cell.replaceWith(th);
      }
      cell = nextCell;
    }
    header = !header;
    firstTR.updateHTML();
  }
  
  void mergeRight([DNTD td]) {
    if (td == null)
      td = getSelectedCell();
    if (td == null)
      return;
    int x = getCellX(td);
    int y = getCellY(td);
    int x2 = x + td.colspan;
    if (x2 >= xLength())
      return;
    DNTD td2 = getCell(x2, y);
    if (td2.parent != td.parent)
      splitY(td2); // TODO: check again, FIXME: make it undoable (pb: get ref to next when it does not exist yet)
    DNTD next = td.nextSibling;
    if (next == null)
      return;
    if (next.rowspan != td.rowspan) {
      //TODO: something ?
      return;
    }
    UndoableEdit edit = new UndoableEdit.compound(Strings.get('table.merge'));
    edit.addSubEdit(
        new UndoableEdit.changeAttribute(td, new DaxeAttr(_colspanAttr, (td.colspan + next.colspan).toString())));
    edit.addSubEdit(new UndoableEdit.removeNode(next));
    doc.doNewEdit(edit);
    page.moveCursorTo(td.firstCursorPositionInside());
    page.updateAfterPathChange();
  }
  
  void mergeBottom([DNTD td]) {
    if (td == null)
      td = getSelectedCell();
    if (td == null)
      return;
    int x = getCellX(td);
    int y = getCellY(td);
    int y2 = y + td.rowspan;
    if (y2 >= yLength())
      return;
    DNTD td2 = getCell(x, y2);
    if (td2.colspan != td.colspan) {
      //TODO: something ?
      return;
    }
    UndoableEdit edit = new UndoableEdit.compound(Strings.get('table.merge'));
    edit.addSubEdit(
        new UndoableEdit.changeAttribute(td, new DaxeAttr(_rowspanAttr, (td.rowspan + td2.rowspan).toString())));
    edit.addSubEdit(new UndoableEdit.removeNode(td2));
    doc.doNewEdit(edit);
    page.moveCursorTo(td.firstCursorPositionInside());
    page.updateAfterPathChange();
  }
  
  void splitX([DNTD td, UndoableEdit parentEdit]) {
    if (td == null)
      td = getSelectedCell();
    if (td == null)
      return;
    if (td.colspan < 2)
      return;
    DNTD newtd;
    if (td is DNTH)
      newtd = new DNTH.fromRef(_thref);
    else
      newtd = new DNTD.fromRef(_tdref);
    DNTR tr = td.parent;
    UndoableEdit edit;
    if (parentEdit != null)
      edit = parentEdit;
    else
      edit = new UndoableEdit.compound(Strings.get('table.split'));
    edit.addSubEdit(
        new UndoableEdit.insertNode(new Position(tr, tr.offsetOf(td) + 1), newtd));
    edit.addSubEdit(
        new UndoableEdit.changeAttribute(td, new DaxeAttr(_colspanAttr, (td.colspan - 1).toString())));
    if (parentEdit == null)
      doc.doNewEdit(edit);
  }
  
  void splitY([DNTD td, UndoableEdit parentEdit]) {
    if (td == null)
      td = getSelectedCell();
    if (td == null)
      return;
    if (td.rowspan < 2)
      return;
    int x = getCellX(td);
    int y = getCellY(td);
    int y2 = y + td.rowspan - 1;
    DNTR tr = childAtOffset(y2);
    if (tr == null)
      return;
    int offset = 0;
    for (DNTD td = tr.firstChild; td != null && x > getCellX(td); td = td.nextSibling) {
      offset++;
    }
    DNTD newtd = new DNTD.fromRef(_tdref);
    UndoableEdit edit;
    if (parentEdit != null)
      edit = parentEdit;
    else
      edit = new UndoableEdit.compound(Strings.get('table.split'));
    edit.addSubEdit(
        new UndoableEdit.insertNode(new Position(tr, offset), newtd));
    edit.addSubEdit(
        new UndoableEdit.changeAttribute(td, new DaxeAttr(_rowspanAttr, (td.rowspan - 1).toString())));
    if (parentEdit == null)
      doc.doNewEdit(edit);
  }
  
  @override
  bool newlineAfter() {
    return(true);
  }
  
  @override
  bool newlineInside() {
    return(true);
  }
}


class DNTR extends DaxeNode {
  
  DNTR.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
    userCannotRemove = true;
    userCannotEdit = true;
  }
  
  DNTR.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent, createChildren: false) {
    userCannotRemove = true;
    userCannotEdit = true;
    fixLineBreaks();
  }
  
  @override
  h.Element html() {
    h.TableRowElement tr = new h.TableRowElement();
    tr.attributes['id'] = "$id";
    tr.attributes['class'] = 'dn';
    DaxeNode dn = firstChild;
    while (dn != null) {
      tr.append(dn.html());
      dn = dn.nextSibling;
    }
    return(tr);
  }
  
  @override
  Position firstCursorPositionInside() {
    if (firstChild == null) {
      return(null);
    }
    return(new Position(firstChild, 0));
  }
  
  @override
  Position lastCursorPositionInside() {
    if (lastChild == null) {
      return(null);
    }
    return(new Position(lastChild, lastChild.offsetLength));
  }
  
  @override
  bool newlineAfter() {
    return(true);
  }
}

class DNTD extends DaxeNode {
  
  DNTD.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
    userCannotRemove = true;
  }
  
  DNTD.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
    userCannotRemove = true;
    fixLineBreaks();
  }
  
  @override
  h.Element html() {
    h.TableCellElement td = new h.TableCellElement();
    td.id = "$id";
    td.classes.add('dn');
    if (this is DNTH)
      td.classes.add('header');
    td.attributes['rowspan'] = rowspan.toString();
    td.attributes['colspan'] = colspan.toString();
    if (align != '')
      td.attributes['align'] = align;
    DaxeNode dn = firstChild;
    while (dn != null) {
      td.append(dn.html());
      dn = dn.nextSibling;
    }
    // without a space at the end, a newline at the end of a cell does not create additional
    // space for the cell
    // but this should not be added after a block, otherwise it adds an empty line.
    // This kind of conditional HTML makes it hard to optimize display updates:
    // we have to override updateHTMLAfterChildrenChange
    if (lastChild == null || !lastChild.block)
      td.appendText(' ');
    return(td);
  }
  
  @override
  void updateHTMLAfterChildrenChange(List<DaxeNode> changed) {
    super.updateHTMLAfterChildrenChange(changed);
    h.TableCellElement td = getHTMLNode();
    if (lastChild == null || !lastChild.block) {
      if (td.lastChild is! h.Text)
        td.appendText(' ');
    } else {
      if (td.lastChild is h.Text)
        td.lastChild.remove();
    }
  }
  
  int get rowspan {
    DNTable table = parent.parent;
    String v = getAttribute(table._rowspanAttr); 
    if (v == null || v == '')
      return(1);
    else
      return(int.parse(v, onError: (String s) => 1));
  }
  
  int get colspan {
    DNTable table = parent.parent;
    String v = getAttribute(table._colspanAttr); 
    if (v == null || v == '')
      return(1);
    else
      return(int.parse(v, onError: (String s) => 1));
  }
  
  String get align {
    DNTable table = parent.parent;
    String v = getAttribute(table._alignAttr); 
    if (v == null || v == '')
      return('');
    else
      return(v);
  }
  
  // TODO: attributes: prevent edit rowspan/colspan ?
}

class DNTH extends DNTD {
  
  DNTH.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
  }
  
  DNTH.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
    fixLineBreaks();
  }
  
}



Index: modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_text.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_text.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of nodes;

/**
 * An editable text node.
 */
class DNText extends DaxeNode {
  
  DNText(String s) : super.text(s) {
  }
  
  DNText.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
    
  }
  
  @override
  h.Element html() {
    h.Element span;
    span = new h.SpanElement();
    span.attributes['id'] = "$id";
    span.attributes['class'] = 'dn';
    if (nodeValue != null) {
      span.append(new h.Text(nodeValue));
    }
    return(span);
  }
  
  void insertString(Position pos, String s) {
    assert(pos.dn == this);
    String v = nodeValue;
    if (v == null)
      v = '';
    nodeValue = "${v.substring(0, pos.dnOffset)}$s${v.substring(pos.dnOffset)}";
  }
  
  /**
   * Cuts this text node at the given [offset] and returns the newly created node.
   */
  DNText cut(int offset) {
    String s1 = nodeValue.substring(0, offset);
    String s2 = nodeValue.substring(offset);
    nodeValue = s1;
    DaxeNode newjn = new DNText(s2);
    parent.insertBefore(newjn, nextSibling);
    return(newjn);
  }
  
  
  @override
  x.Node toDOMNode(x.Document domDocument) {
    return(domDocument.createTextNode(nodeValue));
  }
  
  @override
  bool get noDelimiter {
    return(true);
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_witem.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_witem.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of nodes;

/**
 * A WYSIWYG list item to use with DNWList.
 * Jaxe display type: 'witem'.
 */
class DNWItem extends DaxeNode {
  
  DNWItem.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
  }
  
  DNWItem.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
    fixLineBreaks();
  }
  
  @override
  h.Element html() {
    h.LIElement li = new h.LIElement();
    li.id = "$id";
    li.classes.add('dn');
    DaxeNode dn = firstChild;
    while (dn != null) {
      li.append(dn.html());
      dn = dn.nextSibling;
    }
    return(li);
  }
  
  @override
  h.Element getHTMLContentsNode() {
    return(getHTMLNode());
  }
  
  @override
  bool newlineAfter() {
    return(true);
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_wlist.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/nodes/dn_wlist.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of nodes;

/**
 * A WYSIWYG bulleted list, to use with DNWItem.
 * Jaxe display type: 'wlist' (use with item 'witem').
 * 
 * parameter: 'type': ul/ol (unordered, ordered)
 */
class DNWList extends DaxeNode {
  x.Element _itemref;
  String type;
  
  DNWList.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
    init();
    // this is causing problems with item paste, use DNList.withOneEmptyItem instead
    //if (_itemref != null)
    //  appendChild(new DNItem.fromRef(_itemref));
  }
  
  DNWList.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
    init();
    fixLineBreaks();
    // remove spaces between li
    for (DaxeNode dn in childNodes) {
      if (dn is DNText && dn.nodeValue.trim() == '') {
        removeChild(dn);
      }
    }
  }
  
  void init() {
    _itemref = findItemRef(ref);
    type = doc.cfg.elementParameterValue(ref, 'type', 'ul');
  }
  
  @override
  h.Element html() {
    h.DivElement div = new h.DivElement();
    div.id = "$id";
    div.classes.add('dn');
    if (!valid)
      div.classes.add('invalid');
    h.Element list;
    if (type == 'ul')
      list = new h.UListElement();
    else
      list = new h.OListElement();
    list.classes.add('wlist');
    DaxeNode dn = firstChild;
    while (dn != null) {
      list.append(dn.html());
      dn = dn.nextSibling;
    }
    div.append(list);
    return(div);
  }
  
  @override
  Position firstCursorPositionInside() {
    if (firstChild == null) {
      return(null);
    }
    return(new Position(firstChild, 0));
  }
  
  @override
  Position lastCursorPositionInside() {
    if (lastChild == null) {
      return(null);
    }
    return(new Position(lastChild, lastChild.offsetLength));
  }
  
  @override
  h.Element getHTMLContentsNode() {
    return(getHTMLNode().firstChild);
  }
  
  @override
  bool newlineAfter() {
    return(true);
  }
  
  @override
  bool newlineInside() {
    return(true);
  }
  
  static x.Element findItemRef(x.Element listRef) {
    List<x.Element> subElements = doc.cfg.subElements(listRef);
    if (subElements.length > 0)
      return(subElements[0]);
    return(null);
  }
  
  /**
   * Adds a list on the current line, or changes the type.
   */
  static void addList(x.Element listRef) {
    x.Element itemRef = findItemRef(listRef);
    Position selectionStart = page.getSelectionStart();
    Position selectionEnd = page.getSelectionEnd();
    UndoableEdit edit = new UndoableEdit.compound(doc.cfg.elementTitle(listRef));
    Position newPos;
    
    DNWItem item = new DNWItem.fromRef(itemRef);
    
    // look for first ancestor list
    DaxeNode firstList = selectionStart.dn;
    while (firstList != null && firstList is! DNWList) {
      firstList = firstList.parent;
    }
    if (firstList is DNWList && firstList.ref != listRef) {
      // we are in a list of a different type, its type will be changed
      newPos = new Position.leftOffsetPosition(selectionStart);
      DNWList list = new DNWList.fromRef(listRef);
      for (DaxeNode child = firstList.firstChild; child != null; child=child.nextSibling) {
        list.appendChild(new DaxeNode.clone(child));
      }
      edit.addSubEdit(new UndoableEdit.insertNode(
          new Position(firstList.parent, firstList.parent.offsetOf(firstList)), list));
      edit.addSubEdit(new UndoableEdit.removeNode(firstList));
      doc.doNewEdit(edit);
      page.moveCursorTo(newPos);
      page.updateAfterPathChange();
      return;
    }
    
    // look for first ancestor paragraph
    DaxeNode p = selectionStart.dn;
    while (p != null && p is! DNHiddenP)
      p = p.parent;
    //TODO: something, if selectionStart != selectionEnd
    if (p is DNHiddenP) {
      //NOTE: items inside a hidden paragraph are assumed to be valid under list item
      for (DaxeNode child = p.firstChild; child != null; child=child.nextSibling)
        item.appendChild(new DaxeNode.clone(child));
      if (p.previousSibling is DNWList && p.nextSibling is DNWList) {
        // merge with the 2 lists around
        edit.addSubEdit(new UndoableEdit.insertNode(
            new Position(p.previousSibling, p.previousSibling.offsetLength), item));
        DaxeNode cloneNext = new DaxeNode.clone(p.nextSibling);
        edit.addSubEdit(doc.insertChildrenEdit(cloneNext,
            new Position(p.previousSibling, p.previousSibling.offsetLength + 1)));
        edit.addSubEdit(new UndoableEdit.removeNode(p.nextSibling));
      } else if (p.previousSibling is DNWList) {
        // merge with preceding list
        edit.addSubEdit(new UndoableEdit.insertNode(
            new Position(p.previousSibling, p.previousSibling.offsetLength), item));
      } else if (p.nextSibling is DNWList) {
        // merge with following list
        edit.addSubEdit(new UndoableEdit.insertNode(
            new Position(p.nextSibling, 0), item));
      } else {
        // new list
        DaxeNode list = new DNWList.fromRef(listRef);
        list.appendChild(item);
        edit.addSubEdit(new UndoableEdit.insertNode(
            new Position(p.parent, p.parent.offsetOf(p)), list));
      }
      edit.addSubEdit(new UndoableEdit.removeNode(p));
      newPos = new Position(item, item.offsetLength);
    } else {
      // we are not in a paragraph :(
      // gather all inline nodes around the cursor that can be put into an item
      DaxeNode textParent = selectionStart.dn;
      int offset = selectionStart.dnOffset;
      while (textParent is DNText || textParent is DNStyle) {
        offset = textParent.parent.offsetOf(textParent);
        textParent = textParent.parent;
      }
      
      int startOffset = offset;
      while (startOffset > 0) {
        DaxeNode child = textParent.childAtOffset(startOffset-1);
        if ((child is! DNText && !doc.cfg.isSubElement(itemRef, child.ref)) ||
            child.newlineAfter())
          break;
        startOffset--;
      }
      Position pStart = new Position(textParent, startOffset);
      
      DaxeNode endParent = selectionStart.dn;
      int endOffset = selectionStart.dnOffset;
      while (endParent is DNText || endParent is DNStyle) {
        endOffset = endParent.parent.offsetOf(endParent) + 1;
        endParent = endParent.parent;
      }
      assert(endParent == textParent); // the selection should not cut an element
      while (endOffset < textParent.offsetLength) {
        DaxeNode child = textParent.childAtOffset(endOffset);
        if ((child is! DNText && !doc.cfg.isSubElement(itemRef, child.ref)) ||
            child.newlineAfter())
          break;
        endOffset++;
      }
      Position pEnd = new Position(textParent, endOffset);
      
      DaxeNode clone;
      if (pStart < pEnd) {
        clone = doc.cloneBetween(pStart, pEnd);
        for (DaxeNode child = clone.firstChild; child != null; child = clone.firstChild) {
          clone.removeChild(child);
          item.appendChild(child);
        }
        // remove possible spaces and \n at the beginning and end
        DaxeNode firstChild = item.childAtOffset(0); 
        if (firstChild is DNText) {
          String text = firstChild.nodeValue.trimLeft();
          if (text == '')
            item.removeChild(firstChild);
          else
            firstChild.nodeValue = text;
        }
        DaxeNode lastChild = item.childAtOffset(item.offsetLength-1); 
        if (lastChild is DNText) {
          String text = lastChild.nodeValue.trimRight();
          if (text == '')
            item.removeChild(lastChild);
          else
            lastChild.nodeValue = text;
        }
      }
      
      DaxeNode previousNode;
      if (pStart.dnOffset > 0)
        previousNode = pStart.dn.childAtOffset(pStart.dnOffset - 1);
      else
        previousNode = null;
      DaxeNode nextNode;
      if (pEnd.dnOffset < pEnd.dn.offsetLength)
        nextNode = pEnd.dn.childAtOffset(pEnd.dnOffset);
      else
        nextNode = null;
      if (previousNode is DNWList && nextNode is DNWList) {
        // merge with the 2 lists around
        edit.addSubEdit(new UndoableEdit.insertNode(
            new Position(previousNode, previousNode.offsetLength), item));
        DaxeNode cloneNext = new DaxeNode.clone(nextNode);
        edit.addSubEdit(doc.insertChildrenEdit(cloneNext,
            new Position(previousNode, previousNode.offsetLength + 1)));
        edit.addSubEdit(new UndoableEdit.removeNode(nextNode));
      } else if (previousNode is DNWList) {
        // merge with preceding list
        edit.addSubEdit(new UndoableEdit.insertNode(
            new Position(previousNode, previousNode.offsetLength), item));
      } else if (nextNode is DNWList) {
        // merge with following list
        edit.addSubEdit(new UndoableEdit.insertNode(
            new Position(nextNode, 0), item));
      } else {
        // new list
        DaxeNode list = new DNWList.fromRef(listRef);
        list.appendChild(item);
        edit.addSubEdit(new UndoableEdit.insertNode(pEnd, list));
      }
      if (pStart < pEnd)
        edit.addSubEdit(doc.removeBetweenEdit(pStart, pEnd));
      newPos = new Position(item, item.offsetLength);
    }
    
    doc.doNewEdit(edit);
    page.moveCursorTo(newPos);
    page.updateAfterPathChange();
  }
  
  /**
   * Reduces the list depth of the item at cursor position.
   */
  static void riseLevel() {
    Position start = page.getSelectionStart();
    DaxeNode dn = start.dn;
    while (dn != null && dn is! DNWItem)
      dn = dn.parent;
    if (dn == null)
      return;
    UndoableEdit edit = new UndoableEdit.compound(Strings.get('toolbar.lower_level'));
    DNWItem item = dn;
    DNWList list = item.parent;
    DaxeNode afterList = null;
    if (item.nextSibling != null) {
      afterList = new DNWList.fromRef(list.ref);
      for (dn = item.nextSibling; dn != null; dn = dn.nextSibling) {
        afterList.appendChild(new DaxeNode.clone(dn));
        edit.addSubEdit(new UndoableEdit.removeNode(dn));
      }
    }
    DaxeNode clone = new DaxeNode.clone(item);
    Position newPos;
    if (list.parent is DNWItem) {
      DNWItem parentItem = list.parent;
      DNWList parentList = parentItem.parent;
      if (afterList != null)
        clone.appendChild(afterList);
      edit.addSubEdit(new UndoableEdit.insertNode(
          new Position(parentList, parentList.offsetOf(parentItem) + 1), clone));
      newPos = new Position(clone, clone.offsetLength);
    } else {
      Position after = new Position(list.parent, list.parent.offsetOf(list) + 1);
      if (afterList != null)
        edit.addSubEdit(new UndoableEdit.insertNode(after, afterList));
      if (doc.hiddenParaRefs != null && doc.cfg.findSubElement(list.parent.ref, doc.hiddenParaRefs) != null) {
        x.Element hiddenp = doc.cfg.findSubElement(list.parent.ref, doc.hiddenParaRefs);
        if (clone.firstChild != null) {
          // add paragraphs wherever possible
          DaxeNode current = clone.firstChild;
          DaxeNode next = null;
          DNHiddenP p = null;
          while (current != null) {
            next = current.nextSibling;
            if (current is DNText || doc.cfg.isSubElement(hiddenp, current.ref)) {
              if (p == null)
                p = new DNHiddenP.fromRef(hiddenp);
              clone.removeChild(current);
              p.appendChild(current);
              clone.insertBefore(p, next);
            } else {
              p = null;
            }
            current = next;
          }
          newPos = after;
          edit.addSubEdit(doc.insertChildrenEdit(clone, after));
        } else {
          // add one empty paragraph to replace the list item
          DNHiddenP p = new DNHiddenP.fromRef(hiddenp);
          edit.addSubEdit(new UndoableEdit.insertNode(after, p));
          newPos = new Position(p, 0);
        }
      } else {
        if (clone.firstChild != null) {
          newPos = after;
          edit.addSubEdit(doc.insertChildrenEdit(clone, after));
        } else {
          newPos = after;
        }
      }
    }
    if (item.previousSibling != null)
      edit.addSubEdit(new UndoableEdit.removeNode(item));
    else
      edit.addSubEdit(new UndoableEdit.removeNode(list));
    doc.doNewEdit(edit);
    page.moveCursorTo(newPos);
    page.updateAfterPathChange();
  }
  
  /**
   * Increases the list depth of the item at cursor position.
   */
  static void lowerLevel() {
    Position start = page.getSelectionStart();
    DaxeNode dn = start.dn;
    while (dn != null && dn is! DNWItem)
      dn = dn.parent;
    if (dn == null)
      return;
    DNWItem item = dn;
    DNWItem previousItem = item.previousSibling;
    if (previousItem == null)
      return;
    UndoableEdit edit = new UndoableEdit.compound(Strings.get('toolbar.rise_level'));
    edit.addSubEdit(new UndoableEdit.removeNode(item));
    DaxeNode newItem = new DaxeNode.clone(item);
    if (previousItem.lastChild is DNWList) {
      // merge with previous list
      DNWList previousList = previousItem.lastChild;
      edit.addSubEdit(new UndoableEdit.insertNode(
          new Position(previousList, previousList.offsetLength), newItem));
    } else {
      // create a new list
      DaxeNode newList = new DNWList.fromRef(item.parent.ref);
      newList.appendChild(newItem);
      edit.addSubEdit(new UndoableEdit.insertNode(
          new Position(previousItem, previousItem.offsetLength), newList));
    }
    doc.doNewEdit(edit);
    page.moveCursorTo(new Position(newItem, newItem.offsetLength));
    page.updateAfterPathChange();
  }
  
  /**
   * Called when a newline character is inserted within an item and a new item should be created.
   */
  static void newlineInItem(Position selectionStart) {
    // the newline can be inserted within a style within a hidden paragraph,
    // so the item has to be split with DaxeDocument.cloneCutBetween.
    DNWItem item;
    DaxeNode dn = selectionStart.dn;
    while (dn != null && dn is! DNWItem)
      dn = dn.parent;
    assert(dn != null);
    if (dn == null)
      return;
    item = dn;
    Position beforeItem = new Position(item.parent, item.parent.offsetOf(item));
    Position afterItem = new Position(item.parent, item.parent.offsetOf(item) + 1);
    DaxeNode firstNewItem = doc.cloneCutBetween(item, beforeItem, selectionStart);
    DaxeNode secondNewItem = doc.cloneCutBetween(item, selectionStart, afterItem);
    UndoableEdit edit = new UndoableEdit.compound(Strings.get('undo.insert_text'));
    edit.addSubEdit(new UndoableEdit.insertNode(beforeItem, firstNewItem));
    beforeItem = new Position(item.parent, item.parent.offsetOf(item)+1);
    edit.addSubEdit(new UndoableEdit.insertNode(beforeItem, secondNewItem));
    edit.addSubEdit(new UndoableEdit.removeNode(item));
    doc.doNewEdit(edit);
    page.moveCursorTo(new Position(secondNewItem, 0));
    page.updateAfterPathChange();
  }
  
  static List<x.Element> ulRefs() {
    List<x.Element> list = new List<x.Element>();
    List<x.Element> wlist = doc.cfg.elementsWithType('wlist');
    for (x.Element ref in wlist)
      if (doc.cfg.elementParameterValue(ref, 'type', 'ul') == 'ul')
        list.add(ref);
    return(list);
  }
  
  static List<x.Element> olRefs() {
    List<x.Element> list = new List<x.Element>();
    List<x.Element> wlist = doc.cfg.elementsWithType('wlist');
    for (x.Element ref in wlist)
      if (doc.cfg.elementParameterValue(ref, 'type', 'ul') == 'ol')
        list.add(ref);
    return(list);
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/nodes/nodes.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/nodes/nodes.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

/**
 * Core Daxe nodes. Other Daxe nodes should be added into other libraries and defined as plugins in the config file.
 */

library nodes;

import 'dart:async';
import 'dart:collection';
import 'dart:html' as h;
import 'dart:math';
import 'dart:typed_data';
import 'package:crypto/crypto.dart';
//import 'package:meta/meta.dart';

import '../xmldom/xmldom.dart' as x;
import '../equations/equations.dart' show EquationDialog, MathBase, StringMathBuilder;
import '../strings.dart' show Strings;
import '../../daxe.dart' show doc, page, ActionFunction, CSSMap, DaxeAttr, DaxeNode, DaxeException, HelpDialog,
  NodeFactory, Position, Tag, UndoableEdit, Menu, MenuItem;

part 'dn_anchor.dart';
part 'dn_area.dart';
part 'dn_cdata.dart';
part 'dn_comment.dart';
part 'dn_division.dart';
part 'dn_equa_tex_mem.dart';
part 'dn_document.dart';
part 'dn_empty.dart';
part 'dn_equation_mem.dart';
part 'dn_file.dart';
part 'dn_form.dart';
part 'dn_form_field.dart';
part 'dn_hidden_div.dart';
part 'dn_hidden_p.dart';
part 'dn_hr.dart';
part 'dn_item.dart';
part 'dn_line_break.dart';
part 'dn_list.dart';
part 'parent_updating_dn_text.dart';
part 'dn_processing_instruction.dart';
part 'dn_simple_type.dart';
part 'dn_special.dart';
part 'dn_string.dart';
part 'dn_style.dart';
part 'dn_style_span.dart';
part 'dn_table.dart';
part 'dn_text.dart';
part 'dn_wlist.dart';
part 'dn_witem.dart';
part 'simple_type_control.dart';

Index: modules/damieng/graphical_editor/daxe/lib/src/nodes/parent_updating_dn_text.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/nodes/parent_updating_dn_text.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of nodes;

/**
 * A DNText which triggers a parent node update for each update.
 * (used by DNForm and DNSimpleType)
 * *Warning*: the parent's updateHTML must not call this updateHTML !
 */
class ParentUpdatingDNText extends DNText {
  
  ParentUpdatingDNText(String s) : super(s) {
  }
  
  ParentUpdatingDNText.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
  }
  
  @override
  void updateHTML() {
    parent.updateHTML();
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/nodes/simple_type_control.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/nodes/simple_type_control.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of nodes;

/**
 * Control for an element or attribute with a WXS simple type, used by [DNSimpleType] and [DNForm].
 */
class SimpleTypeControl {
  
  static int _n = 0;
  x.Element refElement;
  x.Element refAttribute;
  String value;
  String _uniqueName;
  List<String> validValues;
  List<String> suggestedValues;
  HashMap<String, String> titleToValue;
  h.Element hcontrol;
  ActionFunction valueChanged;
  bool catchUndo;
  Menu popup;
  
  
  SimpleTypeControl.forElement(this.refElement, this.value, {this.valueChanged}) {
    refAttribute = null;
    _uniqueName = "control$_n";
    _n++;
    validValues = doc.cfg.elementValues(refElement);
    if (validValues == null || validValues.length == 0)
      suggestedValues = doc.cfg.elementSuggestedValues(refElement);
    else {
      if (!validValues.contains(''))
        validValues.add('');
    }
    catchUndo = true;
  }
  
  SimpleTypeControl.forAttribute(this.refElement, this.refAttribute, this.value,
      {this.valueChanged, this.catchUndo: false}) {
    _uniqueName = "control$_n";
    _n++;
    validValues = doc.cfg.attributeValues(refAttribute);
    if (validValues == null || validValues.length == 0)
      suggestedValues = doc.cfg.attributeSuggestedValues(refElement, refAttribute);
    else {
      String defaultValue = doc.cfg.defaultAttributeValue(refAttribute);
      if (!validValues.contains('') && defaultValue == null)
        validValues.add('');
    }
  }
  
  h.Element html() {
    h.SpanElement span = new h.SpanElement();
    bool booleanValues;
    final List<String> lvalbool = ['true', 'false', '1', '0'];
    if (validValues == null || validValues.length != lvalbool.length ||
        (value != null && !lvalbool.contains(value))) {
      booleanValues = false;
    } else {
      booleanValues = true;
      for (int i=0; i<validValues.length; i++) {
        if (validValues[i] != lvalbool[i]) {
          booleanValues = false;
          break;
        }
      }
    }
    if (booleanValues) {
      h.CheckboxInputElement cb = new h.CheckboxInputElement();
      hcontrol = cb;
      if (value == null)
        value = 'false';
      cb.checked = (value == 'true' || value == '1');
      cb.onChange.listen((h.Event event) => checkValue(true));
      span.append(cb);
    } else if (validValues == null || validValues.length == 0) {
      h.TextInputElement input = new h.TextInputElement();
      input.spellcheck = false;
      hcontrol = input;
      input.size = 40;
      if (value == null)
        value = "";
      input.value = value;
      checkValue(false);
      
      // FIXME: this is not optimized:
      input.onInput.listen((h.Event event) => checkValue(true)); // onInput doesn't work with IE9 and backspace
      input.onKeyUp.listen((h.KeyboardEvent event) { // onKeyUp doesn't work with datalists
        bool ctrl = event.ctrlKey || event.metaKey;
        bool shift = event.shiftKey;
        int keyCode = event.keyCode;
        if (catchUndo && ctrl && ((!shift && keyCode == h.KeyCode.Z) ||
            (!shift && keyCode == h.KeyCode.Y) || (shift && keyCode == h.KeyCode.Z))) {
          // undo/redo handled elsewhere
        } else
          checkValue(true);
      });
      
      if (suggestedValues != null && suggestedValues.length > 0) {
        String title;
        if (refAttribute != null)
          title = doc.cfg.attributeValueTitle(refElement, refAttribute, value);
        else
          title = doc.cfg.elementValueTitle(refElement, value);
        input.value = title;
        h.DataListElement datalist = new h.DataListElement();
        datalist.id = 'datalist_$_uniqueName';
        titleToValue = new HashMap<String, String>();
        popup = new Menu('');
        for (String v in suggestedValues) {
          h.OptionElement option = new h.OptionElement();
          String title;
          if (refAttribute != null)
            title = doc.cfg.attributeValueTitle(refElement, refAttribute, v);
          else
            title = doc.cfg.elementValueTitle(refElement, v);
          option.value = title;
          titleToValue[title] = v;
          datalist.append(option);
          popup.add(new MenuItem(title, () {
            input.value = title;
            checkValue(true);
          }));
        }
        input.attributes['list'] = 'datalist_$_uniqueName';
        span.append(datalist);
      }
      span.append(input);
      if (suggestedValues != null && suggestedValues.length > 0) {
        // add a custom menu, because datalist UI sucks and there is no way to configure it or script it safely
        input.style.width = "90%"; // instead of 100%, to give some room to arrowSpan
        h.SpanElement arrowSpan = new h.SpanElement();
        arrowSpan.text = 'â–¼';
        arrowSpan.style.cursor = 'default';
        arrowSpan.onClick.listen((h.Event event) => showSuggestedValuesMenu(input));
        arrowSpan.onMouseOver.listen((h.Event event) => arrowSpan.style.background = '#E0E0E0');
        arrowSpan.onMouseOut.listen((h.Event event) => arrowSpan.style.background = null);
        span.append(arrowSpan);
      }
    } else {
      h.SelectElement select = new h.SelectElement();
      hcontrol = select;
      if (value == null)
        value = '';
      titleToValue = new HashMap<String, String>();
      for (String v in validValues) {
        h.OptionElement option = new h.OptionElement();
        String title;
        if (refAttribute != null)
          title = doc.cfg.attributeValueTitle(refElement, refAttribute, v);
        else
          title = doc.cfg.elementValueTitle(refElement, v);
        option.text = title;
        option.value = v;
        titleToValue[title] = v;
        if (v == value) {
          option.defaultSelected = true;
          option.selected = true;
        }
        select.append(option);
      }
      if (!validValues.contains(value)) {
        // add this invalid value
        h.OptionElement option = new h.OptionElement();
        option.text = value;
        option.selected = true;
        select.append(option);
        hcontrol.classes.add('invalid');
      }
      //select.value = value; // doesn't work with IE
      select.onChange.listen((h.Event event) => checkValue(true));
      span.append(select);
    }
    if (catchUndo) {
      hcontrol.onKeyDown.listen((h.KeyboardEvent event) {
        bool ctrl = event.ctrlKey || event.metaKey;
        bool shift = event.shiftKey;
        int keyCode = event.keyCode;
        if (ctrl && !shift && keyCode == h.KeyCode.Z) { // Ctrl Z
          event.preventDefault();
        } else if (ctrl && ((!shift && keyCode == h.KeyCode.Y) ||
            (shift && keyCode == h.KeyCode.Z))) { // Ctrl-Y and Ctrl-Shift-Z
          event.preventDefault();
        }
      });
      hcontrol.onKeyUp.listen((h.KeyboardEvent event) {
        bool ctrl = event.ctrlKey || event.metaKey;
        bool shift = event.shiftKey;
        int keyCode = event.keyCode;
        if (ctrl && !shift && keyCode == h.KeyCode.Z) { // Ctrl Z
          event.preventDefault();
          doc.undo();
        } else if (ctrl && ((!shift && keyCode == h.KeyCode.Y) ||
            (shift && keyCode == h.KeyCode.Z))) { // Ctrl-Y and Ctrl-Shift-Z
          event.preventDefault();
          doc.redo();
        }
      });
    }
    return(span);
  }
  
  void checkValue(bool callAction) {
    String oldValue = value;
    if (/*hcontrol is h.TextInputElement*/ /* pb with JS: Dart bug 10383 */
        hcontrol is h.InputElement && (hcontrol as h.InputElement).type == 'text') {
      String title = (hcontrol as h.TextInputElement).value;
      if (titleToValue != null && titleToValue[title] != null)
        value = titleToValue[title];
      else
        value = title;
    } else if (hcontrol is h.SelectElement) {
      value = (hcontrol as h.SelectElement).value;
    } else if (/*hcontrol is h.CheckboxInputElement*/
        hcontrol is h.InputElement && (hcontrol as h.InputElement).type == 'checkbox') {
      bool checked = (hcontrol as h.CheckboxInputElement).checked;
      if (checked)
        value = 'true';
      else
        value = 'false';
    }
    bool valid;
    if (refAttribute != null)
      valid = doc.cfg.validAttributeValue(refAttribute, value);
    else
      valid = (value == '' || doc.cfg.isElementValueValid(refElement, value));
    if (valid) {
      hcontrol.classes.add('valid');
      hcontrol.classes.remove('invalid');
    } else {
      hcontrol.classes.add('invalid');
      hcontrol.classes.remove('valid');
    }
    if (callAction && value != oldValue && valueChanged != null)
      valueChanged();
  }
  
  String getValue() => value;
  
  void setValue(String value) {
    this.value = value;
    if (/*hcontrol is h.TextInputElement*/
        hcontrol is h.InputElement && (hcontrol as h.InputElement).type == 'text') {
      h.TextInputElement input = hcontrol as h.TextInputElement;
      if (refAttribute != null)
        input.value = doc.cfg.attributeValueTitle(refElement, refAttribute, value);
      else
        input.value = doc.cfg.elementValueTitle(refElement, value);
    } else if (hcontrol is h.SelectElement) {
      h.SelectElement select = hcontrol as h.SelectElement;
      // reset the options to remove invalid values
      while (select.options.length > 0)
        select.options[0].remove();
      for (String v in validValues) {
        h.OptionElement option = new h.OptionElement();
        if (refAttribute != null)
          option.text = doc.cfg.attributeValueTitle(refElement, refAttribute, v);
        else
          option.text = doc.cfg.elementValueTitle(refElement, v);
        if (v == value)
          option.selected = true;
        select.append(option);
      }
      if (!validValues.contains(value)) {
        // add this invalid value (will be removed at the next call to setValue with another value)
        h.OptionElement option = new h.OptionElement();
        option.text = value;
        option.selected = true;
        select.append(option);
        hcontrol.classes.add('invalid');
      }
      select.value = value;
    } else if (/*hcontrol is h.CheckboxInputElement*/
        hcontrol is h.InputElement && (hcontrol as h.InputElement).type == 'checkbox')
      (hcontrol as h.CheckboxInputElement).checked = (value == 'true' || value == '1');
  }
  
  void focus() {
    //TODO: update insert panel
    // to focus and move the cursor at the end :
    if (hcontrol is h.TextInputElement) {
      h.TextInputElement input = hcontrol as h.TextInputElement;
      // to work around a bug with Firefox (focus doesn't work)
      input.select();
      input.selectionStart = input.selectionEnd = input.value.length;
      // this works in Chromium but not Firefox :
      //input.onFocus.first.then((h.Event event) => input.selectionStart = input.selectionEnd = input.value.length);
      //input.focus();
    } else if (hcontrol != null) {
      hcontrol.focus();
    }
  }
  
  void showSuggestedValuesMenu(h.TextInputElement input) {
    h.DivElement div = popup.htmlMenu();
    div.style.position = 'absolute';
    div.style.display = 'block';
    h.Rectangle rect = input.getBoundingClientRect();
    div.style.left = "${rect.left}px";
    div.style.top = "${rect.bottom}px";
    div.style.width = "${rect.width}px";
    ((div.firstChild) as h.Element).style.width = "${rect.width}px"; // for the table
    h.document.body.append(div);
    StreamSubscription<h.MouseEvent> subscription = h.document.onMouseUp.listen(null);
    subscription.onData((h.MouseEvent event) {
      subscription.cancel();
      div.remove();
      event.preventDefault();
    });
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/daxe_wxs.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/daxe_wxs.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;



/**
 * Class implementing InterfaceSchema for the W3C schemas.
 */
class DaxeWXS implements InterfaceSchema {
  
  WXSSchema _schema;
  /** map: element reference -> WXS element */
  final HashMap<Element, WXSElement> _hElementRefToWXS = new HashMap<Element, WXSElement>();
  /** map: attribute reference -> WXS attribute */
  final HashMap<Element, WXSAttribute> _hAttributeRefToWXS = new HashMap<Element, WXSAttribute>();
  /** map: name -> WXS object */
  final HashMap<String, List<WXSElement>> _hNameToWXS = new HashMap<String, List<WXSElement>>();
  /** map: namespace -> prefix */
  HashMap<String, String> _namespaceToPrefix;
  
  /** list of all WXS elements (not necessarily directly under xs:schema)
  (they can have a name attribute or a ref attribute) */
  final LinkedHashSet<WXSElement> _lAllElements = new LinkedHashSet<WXSElement>();
  Set<WXSSchema> _includedSchemas;
  HashMap<String, String> _hashTitles;
  
  
  DaxeWXS(HashMap<String, String> hashElementTitles) {
    this._hashTitles = hashElementTitles;
    _includedSchemas = new HashSet<WXSSchema>();
    _namespaceToPrefix = new HashMap<String, String>();
  }
  
  Future load(final String schemaURL) {
    Completer completer = new Completer();
    _readDocument(schemaURL).then((Element documentElement) {
      _schema = new WXSSchema(documentElement, schemaURL, this, null);
      _addNamespaces(_schema, null, null);
      _includedSchemas.add(_schema);
      _schema._inclusions().then((_) {
        for (final WXSSchema sch in _includedSchemas)
          _lAllElements.addAll(sch.allElements());
        for (final WXSSchema sch in _includedSchemas)
          sch._resolveReferences(); // WXSAny.resolveReferences() needs this DaxeWXS object, so it is called later
        for (WXSElement element in _lAllElements) {
          _hElementRefToWXS[element.getDOMElement()] = element;
          if (element.getName() != null && element.getRef() == null) {
            List<WXSElement> listeWXS = _hNameToWXS[element.getName()];
            if (listeWXS == null) {
              listeWXS = new List<WXSElement>();
              _hNameToWXS[element.getName()] = listeWXS;
            }
            listeWXS.add(element);
          }
        }
        for (WXSElement element in _lAllElements) {
          final List<WXSAttribute> attributs = element.attributes();
          if (attributs != null) {
            for (WXSAttribute attribut in attributs)
              _hAttributeRefToWXS[attribut.getDOMElement()] = attribut;
          }
        }
        completer.complete();
      });
    }, onError: (WXSException ex) {
      completer.completeError(ex);
    });
    return(completer.future);
  }
  
  // from InterfaceSchema
  bool elementInSchema(final Element elementRef) {
    return(_hElementRefToWXS[elementRef] != null);
  }
  
  // from InterfaceSchema
  Element elementReferenceByName(final String name) {
    final List<WXSElement> listeWXS = _hNameToWXS[name];
    if (listeWXS == null)
      return(null);
    return(listeWXS[0].getDOMElement());
  }
  
  // from InterfaceSchema
  List<Element> elementReferencesByName(final String name) {
    final List<WXSElement> listeWXS = _hNameToWXS[name];
    if (listeWXS == null)
      return(null);
    List<Element> refs = new List<Element>();
    for (WXSElement el in listeWXS)
      refs.add(el.getDOMElement());
    return(refs);
  }
  
  // from InterfaceSchema
  Element elementReference(final Element el, final Element parentRef) {
    if (parentRef == null) {
      // for root elements: local definitions should be avoided
      String name;
      if (el.prefix == null)
        name = el.nodeName;
      else
        name = el.localName;
      final String espace = el.namespaceURI;
      for (final WXSSchema sch in _includedSchemas) {
        for (final WXSElement topel in sch.getTopElements()) {
          if (topel.getRef() == null && !topel.getAbstract() &&
              name == topel.getName() && espace == topel.getNamespace())
            return(topel.getDOMElement());
        }
      }
      print("DaxeWXS: elementReference: no matching root element in the schema for $name");
      return(null);
    }
    final WXSElement wxsParent = _hElementRefToWXS[parentRef];
    if (wxsParent == null) {
      print("DaxeWXS: elementReference: unknown element reference: $parentRef");
      return(null);
    }
    final List<WXSElement> liste = wxsParent.subElements();
    final String name = el.localName;
    final String namespace = el.namespaceURI;
    for (final WXSElement element in liste) {
      if (element.getName() == name && element.getNamespace() == namespace)
        return(element.getDOMElement());
    }
    return(null);
  }
  
  // from InterfaceSchema
  String elementName(final Element elementRef) {
    final WXSElement element = _hElementRefToWXS[elementRef];
    if (element == null) {
      print("DaxeWXS: elementName: unknown element reference: $elementRef");
      return(null);
    }
    return(element.getName());
  }
  
  // from InterfaceSchema
  String elementNamespace(final Element elementRef) {
    final WXSElement element = _hElementRefToWXS[elementRef];
    if (element == null)
      return(null);
    return(element.getNamespace());
  }
  
  // from InterfaceSchema
  String elementPrefix(final Element elementRef) {
    final String namespace = elementNamespace(elementRef);
    if (namespace == null)
      return(null);
    return(_namespaceToPrefix[namespace]);
  }
  
  // from InterfaceSchema
  String elementDocumentation(final Element elementRef) {
    final WXSElement element = _hElementRefToWXS[elementRef];
    if (element == null)
      return(null);
    String doc = element.getDocumentation();
    if (doc != null)
      return(doc);
    if (element._complexType != null)
      return(element._complexType.getDocumentation());
    return(null);
  }
  
  // from InterfaceSchema
  List<String> elementValues(final Element elementRef) {
    final WXSElement element = _hElementRefToWXS[elementRef];
    if (element == null)
      return(null);
    return(element.possibleValues());
  }
  
  // from InterfaceSchema
  List<String> suggestedElementValues(final Element elementRef) {
    final WXSElement element = _hElementRefToWXS[elementRef];
    if (element == null)
      return(null);
    return(element.suggestedValues());
  }
  
  // from InterfaceSchema
  bool elementValueIsValid(final Element elementRef, final String value) {
    final WXSElement element = _hElementRefToWXS[elementRef];
    if (element == null)
      return(false);
    return(element.validValue(value));
  }
  
  // from InterfaceSchema
  List<String> namespaceList() {
    final LinkedHashSet<String> set = new LinkedHashSet<String>();
    if (_schema.getTargetNamespace() != null)
      set.add(_schema.getTargetNamespace());
    for (final String s in _namespaceToPrefix.keys)
      set.add(s);
    return(new List.from(set));
  }
  
  // from InterfaceSchema
  bool hasNamespace(final String namespace) {
    final String targetNamespace = _schema.getTargetNamespace();
    if (namespace == null) {
      if (targetNamespace == null || targetNamespace == "")
        return(true);
      if (_namespaceToPrefix.containsKey(""))
        return(true);
      // case of local elements without a namespace :
      final bool qualified = ("qualified" == _schema.getElementFormDefault());
      if (!qualified)
        return(true);
    } else {
      if (namespace == targetNamespace)
        return(true);
      if (_namespaceToPrefix.containsKey(namespace))
        return(true);
    }
    return(false);
  }
  
  // from InterfaceSchema
  String namespacePrefix(final String ns) {
    return(_namespaceToPrefix[ns]);
  }
  
  // from InterfaceSchema
  String getTargetNamespace() {
    return(_schema.getTargetNamespace());
  }
  
  // from InterfaceSchema
  List<Element> elementsOutsideNamespace(final String namespace) {
    final List<Element> liste = new List<Element>();
    for (final WXSElement el in _lAllElements) {
      if (el.getName() != null && el.getRef() == null && !el.getAbstract()) {
        final String tns = el.getNamespace();
        if (tns != null && tns != namespace)
          liste.add(el.getDOMElement());
      }
    }
    return(liste);
  }
  
  // from InterfaceSchema
  List<Element> elementsWithinNamespaces(final Set<String> namespaces) {
    final List<Element> liste = new List<Element>();
    for (final WXSElement el in _lAllElements) {
      if (el.getName() != null && el.getRef() == null && !el.getAbstract()) {
        final String tns = el.getNamespace();
        if (tns != null && namespaces.contains(tns))
          liste.add(el.getDOMElement());
      }
    }
    return(liste);
  }
  
  // from InterfaceSchema
  List<Element> allElements() {
    final List<Element> liste = new List<Element>();
    for (final WXSElement el in _lAllElements) {
      if (el.getName() != null && el.getRef() == null && !el.getAbstract())
        liste.add(el.getDOMElement());
    }
    return(liste);
  }
  
  // from InterfaceSchema
  List<Element> rootElements() {
    final List<Element> liste = new List<Element>();
    for (final WXSSchema sch in _includedSchemas) {
      for (final WXSElement el in sch.getTopElements()) {
        if (el.getName() != null && el.getRef() == null && !el.getAbstract())
          liste.add(el.getDOMElement());
      }
    }
    return(liste);
  }
  
  // from InterfaceSchema
  bool requiredElement(final Element parentRef, final Element childRef) {
    final WXSElement parent = _hElementRefToWXS[parentRef];
    if (parent == null) {
      print("DaxeWXS: requiredElement: unknown element reference: $parentRef");
      return(false);
    }
    final WXSElement child = _hElementRefToWXS[childRef];
    if (child == null) {
      print("DaxeWXS: requiredElement: unknown element reference: $childRef");
      return(false);
    }
    bool bb = parent.requiredChild(child);
    return(bb != null && bb);
  }
  
  // from InterfaceSchema
  bool multipleChildren(final Element parentRef, final Element childRef) {
    final WXSElement parent = _hElementRefToWXS[parentRef];
    if (parent == null) {
      print("DaxeWXS: multipleChildren: unknown element reference: $parentRef");
      return(false);
    }
    final WXSElement child = _hElementRefToWXS[childRef];
    if (child == null) {
      print("DaxeWXS: multipleChildren: unknown element reference: $childRef");
      return(false);
    }
    bool bb = parent.multipleChildren(child);
    return(bb != null && bb);
  }
  
  // from InterfaceSchema
  List<Element> subElements(final Element parentRef) {
    assert(parentRef != null);
    // à faire: cache
    final WXSElement parent = _hElementRefToWXS[parentRef];
    if (parent == null) {
      print("DaxeWXS: subElements: unknown element reference: $parentRef");
      return(null);
    }
    final List<WXSElement> sousElements = parent.subElements();
    final List<Element> liste = new List<Element>();
    for (WXSElement element in sousElements)
      liste.add(element.getDOMElement());
    return(liste);
  }
  
  /**
   * Regular expression for a given parent element.
   * In DaxeWXS, modevisu=true and modevalid=true.
   * [modevisu]  True to get a regular expression to display to the user
   * [modevalid]  For strict validation instead of checking if an insert is possible
   */
  // from InterfaceSchema
  String regularExpression(final Element parentRef, final bool modevisu, final bool modevalid) {
    final WXSElement parent = _hElementRefToWXS[parentRef];
    if (parent == null) {
      print("DaxeWXS: regularExpression: unknown element reference: $parentRef");
      return(null);
    }
    return(parent.elementRegularExpression()); // on utilise toujours modevisu=true et modevalid=true
  }
    
  // from InterfaceSchema
  List<Element> parentElements(final Element elementRef) {
    final WXSElement element = _hElementRefToWXS[elementRef];
    if (element == null) {
      print("DaxeWXS: parentElements: unknown element reference: $elementRef");
      return(null);
    }
    final List<WXSElement> parents = element.parentElements();
    final List<Element> liste = new List<Element>();
    for (WXSElement el in parents)
      liste.add(el.getDOMElement());
    return(liste);
  }
  
  // from InterfaceSchema
  List<Element> elementAttributes(final Element elementRef) {
    final WXSElement element = _hElementRefToWXS[elementRef];
    if (element == null) {
      print("DaxeWXS: elementAttributes: unknown element reference: $elementRef");
      return(null);
    }
    final List<WXSAttribute> attributes = element.attributes();
    final List<Element> liste = new List<Element>();
    for (WXSAttribute attribute in attributes)
      liste.add(attribute.getDOMElement());
    return(liste);
  }
  
  // from InterfaceSchema
  String attributeName(final Element attributeRef) {
    final WXSAttribute attribute = _hAttributeRefToWXS[attributeRef];
    if (attribute == null) {
      print("DaxeWXS: attributeName: unknown attribute reference: $attributeRef");
      return(null);
    }
    return(attribute.getName());
  }
  
  // from InterfaceSchema
  String attributeNamespace(final Element attributeRef) {
    final WXSAttribute attribute = _hAttributeRefToWXS[attributeRef];
    if (attribute == null) {
      print("DaxeWXS: attributeNamespace: unknown attribute reference: $attributeRef");
      return(null);
    }
    return(attribute.getNamespace());
  }
  
  // from InterfaceSchema
  String attributeDocumentation(final Element attributeRef) {
    final WXSAttribute attribute = _hAttributeRefToWXS[attributeRef];
    if (attribute == null) {
      print("DaxeWXS: attributeDocumentation: unknown attribute reference: $attributeRef");
      return(null);
    }
    return(attribute.getDocumentation());
  }
  
  // from InterfaceSchema
  String attributeNamespaceByName(final String attributeName) {
    if (attributeName == null)
      return(null);
    final String prefix = _namePrefix(attributeName);
    if (prefix == null)
      return(null);
    if (prefix == "xml")
      return("http://www.w3.org/XML/1998/namespace");
    return(_schema.prefixNamespace(prefix));
  }
  
  /**
   * Returns true if an attribute is required, based on its definition.
   * Warning: this is not possible to determine this with only the attribute reference with Relax NG.
   * It is better to use attributeIsRequired.
   */
  bool isRequired(final Element attributeRef) {
    final WXSAttribute attribute = _hAttributeRefToWXS[attributeRef];
    if (attribute == null) {
      print("DaxeWXS: isRequired: unknown attribute reference: $attributeRef");
      return(false);
    }
    return(attribute.getUse() == "required");
  }
  
  // from InterfaceSchema
  bool attributeIsRequired(final Element parentRef, final Element attributeRef) {
    return(isRequired(attributeRef));
  }
  
  // from InterfaceSchema
  List<String> attributeValues(final Element attributeRef) {
    final WXSAttribute attribute = _hAttributeRefToWXS[attributeRef];
    if (attribute == null) {
      print("DaxeWXS: attributeValues: unknown attribute reference: $attributeRef");
      return(null);
    }
    return(attribute.possibleValues());
  }
  
  // from InterfaceSchema
  List<String> suggestedAttributeValues(final Element attributeRef) {
    final WXSAttribute attribute = _hAttributeRefToWXS[attributeRef];
    if (attribute == null) {
      print("DaxeWXS: suggestedAttributeValues: unknown attribute reference: $attributeRef");
      return(null);
    }
    return(attribute.suggestedValues());
  }
  
  // from InterfaceSchema
  String defaultAttributeValue(final Element attributeRef) {
    final WXSAttribute attribute = _hAttributeRefToWXS[attributeRef];
    if (attribute == null) {
      print("DaxeWXS: defaultAttributeValue: unknown attribute reference: $attributeRef");
      return(null);
    }
    return(attribute.defaultValue());
  }
  
  // from InterfaceSchema
  bool attributeIsValid(final Element attributeRef, final String value) {
    // à refaire avec les classes WXS
    final WXSAttribute attribute = _hAttributeRefToWXS[attributeRef];
    if (attribute == null) {
      print("DaxeWXS: attributeIsValid: unknown attribute reference: $attributeRef");
      return(false);
    }
    return(attribute.validValue(value));
  }
  
  // from InterfaceSchema
  Element attributeParent(final Element attributeRef) {
    final WXSAttribute attribute = _hAttributeRefToWXS[attributeRef];
    if (attribute == null) {
      print("DaxeWXS: attributeParent: unknown attribute reference: $attributeRef");
      return(null);
    }
    final List<WXSElement> parents = attribute.parentElements();
    if (parents.length > 0)
      return(parents[0].getDOMElement());
    return(null);
  }
  
  // from InterfaceSchema
  bool canContainText(final Element elementRef) {
    final WXSElement element = _hElementRefToWXS[elementRef];
    if (element == null) {
      print("DaxeWXS: canContainText: unknown element reference: $elementRef");
      return(false);
    }
    return(element.containsText());
  }
  
  
  /**
   * Tests if an element is valid.
   * If insert is true, just tests the validity of an insert (all sub-elements are optionnal).
   */
  bool validElement(final Element elementRef, final List<Element> subElementRefs, final bool insert) {
    final WXSElement element = _hElementRefToWXS[elementRef];
    if (element == null) {
      print("DaxeWXS: validElement: unknown element reference: $elementRef");
      return(false);
    }
    final List<WXSElement> subElements = new List<WXSElement>();
    for (Element ref in subElementRefs) {
      final WXSElement subElement = _hElementRefToWXS[ref];
      if (subElement != null)
        subElements.add(subElement); // else ref from another config ?
    }
    return(element.validateSubElements(subElements, insert));
  }
  
  
  Future<WXSSchema> _newIncludedSchema(final String parentSchemaURL, final String schemaLocation,
                                final String importNamespace, final WXSSchema parentSchema) { //  throws WXSException
    String urls;
    if (schemaLocation.startsWith("http"))
      urls = schemaLocation;
    else
      urls = "${_getParentURL(parentSchemaURL)}/$schemaLocation";
    if (urls == null)
      return(new Future.error(new WXSException("include/import : location not found : $schemaLocation")));
    for (WXSSchema includedSchema in _includedSchemas) {
      if (_normalizePath(includedSchema.getURL()) == _normalizePath(urls)) {
        _addNamespaces(includedSchema, parentSchema, importNamespace); // another chance to find a prefix
        return(new Future.value(includedSchema));
      }
    }
    Completer<WXSSchema> completer = new Completer<WXSSchema>();
    _readDocument(urls).then((Element documentElement) {
      final WXSSchema schemaInclu = new WXSSchema(documentElement, urls, this, parentSchema);
      _addNamespaces(schemaInclu, parentSchema, importNamespace);
      _includedSchemas.add(schemaInclu);
      schemaInclu._inclusions().then((_) {
        completer.complete(schemaInclu);
      }, onError: (WXSException ex) {
        completer.completeError(new WXSException("include/import: $ex"));
      });
    }, onError: (WXSException ex) {
      completer.completeError(new WXSException("include/import: $ex"));
    });
    return(completer.future);
  }
  
  // transforms 'a/..' into '' inside paths
  String _normalizePath(String path) {
    int ind1 = path.indexOf('/');
    if (ind1 == -1)
      return(path);
    String p1 = path.substring(0, ind1);
    int ind2 = path.substring(ind1 + 1).indexOf('/');
    if (ind2 == -1)
      return(path);
    ind2 += ind1 + 1;
    String p2 = path.substring(ind1 + 1, ind2);
    if (p1 != '..' && p2 == '..')
      return(_normalizePath(path.substring(ind2 + 1)));
    else
      return("${path.substring(0, ind1 + 1)}${_normalizePath(path.substring(ind1 + 1))}");
  }
  
  void _addNamespaces(final WXSSchema sch, final WXSSchema parentSchema, final String importNamespace) {
    if (importNamespace != null && _namespaceToPrefix[importNamespace] == null) {
      String prefix = sch.namespacePrefix(importNamespace);
      if (prefix != null)
        _namespaceToPrefix[importNamespace] = prefix;
      else if (parentSchema != null) {
        prefix = parentSchema.namespacePrefix(importNamespace);
        if (prefix != null)
          _namespaceToPrefix[importNamespace] = prefix;
      }
    }
    // always add targetNamespace ?
    final String targetNamespace = sch.getTargetNamespace();
    if (targetNamespace != null && targetNamespace != "") {
      final String prefix = sch.namespacePrefix(targetNamespace);
      if (prefix != null)
        _namespaceToPrefix[targetNamespace] = prefix;
    }
  }
  
  WXSElement _findFirstElement(final String name, final String namespace) {
    final List<WXSElement> listeWXS = _hNameToWXS[name];
    if (listeWXS == null)
      return(null);
    for (WXSElement element in listeWXS) {
      if (namespace == element.getNamespace())
        return(element);
    }
    return(null);
  }
  
  /**
   * Returns the root element of the document with the given URL.
   */
  static Future<Element> _readDocument(final String schemaURL) { // throws WXSException
    Completer<Element> completer = new Completer<Element>();
    DOMParser dp = new DOMParser();
    dp.parseFromURL(schemaURL).then((Document schemadoc) {
      completer.complete(schemadoc.documentElement);
    }, onError: (DOMException ex) {
      print("DaxeWXS: Error reading $schemaURL: $ex");
      completer.completeError(new WXSException("DaxeWXS: reading $schemaURL: $ex"));
    });
    return(completer.future);
  }
  
  /**
   * Returns the URL of the parent directory of the file or directory corresponding to the given URL,
   * or null if the parent directory cannot be determined.
   */
  static String _getParentURL(final String u) {
    final int ind = u.lastIndexOf("/");
    if (ind == -1) {
      return(null);
    } else {
      return(u.substring(0, ind));
    }
  }
  
  List<WXSElement> _anies(final String namespace, final String targetNamespace) {
    final List<WXSElement> liste = new List<WXSElement>();
    if (namespace == null || namespace == "" || namespace == "##any") {
      for (final WXSElement el in _lAllElements)
        if (el.getName() != null && el.getRef() == null && !el.getAbstract())
          liste.add(el);
    } else if (namespace == "##local") {
      for (final WXSElement el in _lAllElements) {
        if (el.getName() != null && el.getRef() == null && !el.getAbstract()) {
          final String tns = el.getNamespace();
          if (tns == null || tns == targetNamespace)
            liste.add(el);
        }
      }
    } else if (namespace == "##other") {
      for (final WXSElement el in _lAllElements) {
        if (el.getName() != null && el.getRef() == null && !el.getAbstract()) {
          final String tns = el.getNamespace();
          if (tns != null && tns != targetNamespace)
            liste.add(el);
        }
      }
    } else {
      // list of namespaces separated by spaces
      final HashSet<String> espaces = new HashSet.from(namespace.split(new RegExp(r"\s+")));
      if (espaces.contains("##targetNamespace")) {
        espaces.remove("##targetNamespace");
        espaces.add(targetNamespace);
      }
      if (espaces.contains("##local")) {
        espaces.remove("##local");
        espaces.add("");
      }
      for (final WXSElement el in _lAllElements) {
        if (el.getName() != null && el.getRef() == null && !el.getAbstract()) {
          final String tns = el.getNamespace();
          if (tns != null && espaces.contains(tns))
            liste.add(el);
        }
      }
    }
    return(liste);
  }
  
  /**
   * Returns the local part of an element's name (by removing the prefix)
   */
  static String _localValue(final String name) {
    if (name == null)
      return(null);
    final int ind = name.indexOf(':');
    if (ind == -1)
      return(name);
    return(name.substring(ind + 1));
  }
  
  /**
   * Returns the name's prefix or null if it does not have one.
   */
  static String _namePrefix(final String name) {
    if (name == null)
      return(null);
    final int indp = name.indexOf(':');
    if (indp == -1)
      return(null);
    else
      return(name.substring(0, indp));
  }
  
  static List<String> _booleanValues(final String type, final Element domElement) {
    final String tns = domElement.lookupNamespaceURI(_namePrefix(type));
    final String schemaNamespace = domElement.namespaceURI;
    if (_localValue(type) == "boolean" && schemaNamespace == tns) {
      final List<String> valbool = ["true", "false", "1", "0"];
      return(valbool);
    }
    return(null);
  }
  
  String _elementTitle(final WXSElement el) {
    if (_hashTitles[el.getName()] != null)
      return(_hashTitles[el.getName()]);
    else
      return(el.getName());
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/parent.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/parent.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;

/**
 * Parent of other elements in the schema
 * (for instance attributeGroup is a possible parent for attribute),
 * except the parents which don't have a parent themselves (schema is excluded).
 */
abstract class Parent {
    
    /**
     * Returns the list of WXSElement which can be parent of this object.
     */
    List<WXSElement> parentElements();
    
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/with_sub_elements.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/with_sub_elements.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;


/**
 * Represents an element from an XML schema, which can contain sub-elements or point to sub-elements.
 */
abstract class WithSubElements {
  
  /**
   * Resolves the references to other schema elements for all the descendants of this schema element.
   */
  void resolveReferences(final WXSSchema schema, final WXSThing redefine);
  
  /**
   * Returns the list of the elements defined inside this model (including all the descendants, but without using element references).
   */
  List<WXSElement> allElements();
  
  /**
   * Returns the list of all the elements directly inside this model (including the ones using references).
   */
  List<WXSElement> subElements();
  
  /**
   * Returns a regular expression for this model, destined to be read by an end-user (but not usable for anything else).
   */
  String regularExpression();
  
  /**
   * Returns true if the element is a required child in this model.
   * Returns null if the given child is actually not a child of this element.
   */
  bool requiredChild(final WXSElement child);
  
  /**
   * Retuns true if the element passed in parameter can be found several times in this model because of a maxOccurs > 1.
   * Returns null if the given child is actually not a child of this element.
   */
  bool multipleChildren(final WXSElement child);
  
  /**
   * Returns the position in the list as far as validation is possible
   * ([start] if no validation is possible, [subElements].length if everything is validated).
   */
  int validate(final List<WXSElement> subElements, final int start, final bool insertion);
  
  /**
   * Retuns true if everything in this model is optionnal, for instance for a sequence containing only optionnal elements.
   */
  bool isOptionnal();
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

/**
 * API for W3C XML Schemas. The main class is [DaxeWXS].
 */
library wxs;

import 'dart:async';
import 'dart:collection';
import 'dart:math' show LN2, log;

import '../interface_schema.dart';

import '../xmldom/xmldom.dart';

part "with_sub_elements.dart";
part "daxe_wxs.dart";
part "parent.dart";
part "wxs_all.dart";
part "wxs_annotated.dart";
part "wxs_annotation.dart";
part "wxs_any.dart";
part "wxs_attribute.dart";
part "wxs_attribute_group.dart";
part "wxs_choice.dart";
part "wxs_complex_content.dart";
part "wxs_complex_type.dart";
part "wxs_documentation.dart";
part "wxs_element.dart";
part "wxs_exception.dart";
part "wxs_explicit_group.dart";
part "wxs_extension.dart";
part "wxs_facet.dart";
part "wxs_field.dart";
part "wxs_group.dart";
part "wxs_import.dart";
part "wxs_include.dart";
part "wxs_key.dart";
part "wxs_keybase.dart";
part "wxs_keyref.dart";
part "wxs_list.dart";
part "wxs_redefine.dart";
part "wxs_restriction.dart";
part "wxs_schema.dart";
part "wxs_selector.dart";
part "wxs_sequence.dart";
part "wxs_simple_content.dart";
part "wxs_simple_type.dart";
part "wxs_thing.dart";
part "wxs_type.dart";
part "wxs_union.dart";
part "wxs_unique.dart";



Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_all.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_all.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;


class WXSAll extends WXSAnnotated implements WithSubElements, Parent {
  
  List<WXSElement> _elements; // (element)*
  int _minOccurs = 1; // 0 | 1
  int _maxOccurs = 1; // 1
  
  Parent _parent; // WXSComplexType | WXSGroup | WXSRestriction | WXSExtension
  
  
  WXSAll(final Element el, final Parent parent, final WXSSchema schema) {
    _parseAnnotation(el);
    _elements = new List<WXSElement>();
    for (Node n = el.firstChild; n != null; n=n.nextSibling) {
      if (n is Element && n.localName == "element")
        _elements.add(new WXSElement(n as Element, this, schema));
    }
    try {
      if (el.hasAttribute("minOccurs"))
        _minOccurs = int.parse(el.getAttribute("minOccurs"));
    } on FormatException {
    }
    this._parent = parent;
  }
  
  // from WithSubElements
  void resolveReferences(final WXSSchema schema, final WXSThing redefine) {
    for (WXSElement element in _elements)
      element.resolveReferences(schema, redefine);
  }
  
  // from WithSubElements
  List<WXSElement> allElements() {
    final List<WXSElement> liste = new List<WXSElement>();
    for (WXSElement element in _elements)
      liste.addAll(element.allElements());
    return(liste);
  }
  
  // from WithSubElements
  List<WXSElement> subElements() {
    final List<WXSElement> liste = new List<WXSElement>();
    for (WXSElement element in _elements)
      liste.addAll(element.matchingElements());
    return(liste);
  }
  
  // from Parent
  List<WXSElement> parentElements() {
    if (_parent != null)
      return(_parent.parentElements());
    return(new List<WXSElement>());
  }
  
  // from WithSubElements
  String regularExpression() {
    final StringBuffer sb = new StringBuffer();
    sb.write('(');
    bool premier = true;
    for (WXSElement element in _elements) {
      final String er = element.regularExpression();
      if (er != null) {
        if (!premier)
          sb.write(" & ");
        premier = false;
        sb.write(er);
      }
    }
    sb.write(')');
    if (_minOccurs == 0)
      sb.write('?');
    return(sb.toString());
  }
  
  // from WithSubElements
  bool requiredChild(final WXSElement child) {
    // renvoie null si l'enfant n'en est pas un
    for (WXSElement element in _elements)
      for (WXSElement elc in element.matchingElements())
        if (elc == child)
          return(_minOccurs > 0 && element.getMinOccurs() > 0);
    return(null);
  }
  
  // from WithSubElements
  bool multipleChildren(final WXSElement child) {
    // renvoie null si l'enfant n'en est pas un
    for (WXSElement element in _elements)
      for (WXSElement elc in element.matchingElements())
        if (elc == child)
          return(false);
    return(null);
  }
  
  // from WithSubElements
  int validate(final List<WXSElement> subElements, final int start, final bool insertion) {
    if (_elements.length == 0)
      return(start);
    List<int> occurences = new List<int>(_elements.length);
    for (int i=0; i<_elements.length; i++)
      occurences[i] = 0;
    int nb = 0;
    for (int i=start; i<subElements.length; i++) {
      final WXSElement sousElement = subElements[i];
      bool trouve = false;
      for (int j=0; j<_elements.length; j++) {
        if (sousElement == _elements[j]) {
          trouve = true;
          occurences[j]++;
          break;
        }
      }
      if (!trouve)
        break;
      nb++;
    }
    for (int i=0; i<_elements.length; i++)
      if (occurences[i] > 1)
        return(start);
    if (!insertion) {
      for (int i=0; i<_elements.length; i++)
        if (occurences[i] == 0 && !_elements[i].isOptionnal())
          return(start);
    }
    return(start + nb);
  }
  
  // from WithSubElements
  bool isOptionnal() {
    if (_elements.length == 0)
      return(true);
    if (_minOccurs == 0)
      return(true);
    for (WXSElement element in _elements) {
      if (!element.isOptionnal())
        return(false);
    }
    return(true);
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_annotated.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_annotated.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;


abstract class WXSAnnotated implements WXSThing {
  
  WXSAnnotation _annotation = null;
  
  
  WXSAnnotation getAnnotation() {
    return(_annotation);
  }
  
  void _parseAnnotation(final Element el) {
    for (Node n = el.firstChild; n != null; n=n.nextSibling) {
      if (n is Element && n.localName == "annotation") {
        _annotation = new WXSAnnotation(n as Element);
        break;
      }
    }
  }
  
  String getDocumentation() {
    if (_annotation == null)
      return(null);
    return(_annotation.getDocumentation());
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_annotation.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_annotation.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;

class WXSAnnotation implements WXSThing {
  
  // (documentation*)
  List<WXSDocumentation> _documentations;
  
  
  WXSAnnotation(final Element el) {
    _documentations = new List<WXSDocumentation>();
    for (Node n = el.firstChild; n != null; n=n.nextSibling) {
      if (n is Element && n.localName == "documentation") {
        _documentations.add(new WXSDocumentation(n as Element));
        break;
      }
    }
  }
  
  String getDocumentation() {
    if (_documentations == null)
      return(null);
    StringBuffer sb = new StringBuffer();
    for (WXSDocumentation doc in _documentations) {
      if (doc.getValue() != null)
        sb.write(doc.getValue());
    }
    return(sb.toString());
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_any.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_any.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;


class WXSAny extends WXSAnnotated implements WithSubElements {
  
  String _namespace = "##any"; // ( (##any | ##other) | List of (anyURI | (##targetNamespace | ##local)) )
  String _processContents = "strict"; // (skip|lax|strict)
  int _minOccurs = 1;
  int _maxOccurs = 1;
  
  WXSExplicitGroup _parent;
  WXSSchema _schema;
  List<WXSElement> _elements;
  
  
  WXSAny(final Element el, final WXSExplicitGroup parent, final WXSSchema schema) {
    if (el.hasAttribute("namespace"))
      _namespace = el.getAttribute("namespace");
    if (el.hasAttribute("processContents"))
      _processContents = el.getAttribute("processContents");
    try {
      if (el.hasAttribute("minOccurs"))
        _minOccurs = int.parse(el.getAttribute("minOccurs"));
      if (el.hasAttribute("maxOccurs")) {
        if (el.getAttribute("maxOccurs") == "unbounded")
          _maxOccurs = 9007199254740992; // Integer.MAX_VALUE
        else
          _maxOccurs = int.parse(el.getAttribute("maxOccurs"));
      }
    } on FormatException {
    }
    
    this._parent = parent;
    this._schema = schema;
    _elements = null;
  }
  
  // from WithSubElements
  void resolveReferences(final WXSSchema schema, final WXSThing redefine) {
    // la résolution nécessite que le schéma soit déjà construit, on doit donc la faire plus tard
    _elements = new List<WXSElement>();
    _elements.addAll(schema.anies(_namespace));
    for (WXSElement element in _elements)
      element.addReference(this);
  }
  
  // from WithSubElements
  List<WXSElement> allElements() {
    return(new List<WXSElement>());
  }
  
  // from WithSubElements
  List<WXSElement> subElements() {
    if (_elements == null)
      resolveReferences(_schema, null);
    return(_elements);
  }
  
  List<WXSElement> parentElements() {
    return(_parent.parentElements());
  }
  
  // from WithSubElements
  String regularExpression() {
    if (_elements == null)
      resolveReferences(_schema, null);
    final StringBuffer sb = new StringBuffer();
    sb.write('(');
    for (int i=0; i<_elements.length; i++) {
      sb.write(_schema.elementTitle(_elements[i]));
      if (i != _elements.length - 1)
        sb.write('|');
    }
    sb.write(')');
    if (_minOccurs == 0 && _maxOccurs == 1)
      sb.write('?');
    else if (_minOccurs == 0 && _maxOccurs > 1)
      sb.write('*');
    else if (_minOccurs > 0 && _maxOccurs > 1)
      sb.write('+');
    return(sb.toString());
  }
  
  // from WithSubElements
  bool requiredChild(final WXSElement child) {
    if (_elements == null)
      resolveReferences(_schema, null);
    // renvoie null si l'enfant n'en est pas un
    if (_elements.contains(child))
      return(_minOccurs > 0 && _elements.length == 1);
    else
      return(null);
  }
  
  // from WithSubElements
  bool multipleChildren(final WXSElement child) {
    if (_elements == null)
      resolveReferences(_schema, null);
    // renvoie null si l'enfant n'en est pas un
    if (_elements.contains(child))
      return(_maxOccurs > 1);
    else
      return(null);
  }
  
  // from WithSubElements
  int validate(final List<WXSElement> subElements, final int start, final bool insertion) {
    if (_elements == null)
      resolveReferences(_schema, null);
    if (!insertion && subElements.length < _minOccurs)
      return(start);
    for (int i=start; i<subElements.length; i++) {
      if (i-start >= _maxOccurs)
        return(i);
      if (!_elements.contains(subElements[i])) {
        if (!insertion && i-start < _minOccurs)
          return(start);
        return(i);
      }
    }
    return(subElements.length);
  }
  
  // from WithSubElements
  bool isOptionnal() {
    return(_minOccurs == 0);
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_attribute.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_attribute.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;


class WXSAttribute extends WXSAnnotated {
  
  WXSSimpleType _simpleType = null;
  String _name = null;
  String _ref = null;
  String _type = null;
  String _use = null; // (prohibited|optional|required)
  String _defaultAtt = null;
  String _fixed = null;
  String _form = null; // (qualified|unqualified)
  
  WXSAttribute _wxsRef = null;
  Element _domElement;
  Parent _parent; // WXSComplexType | WXSRestriction | WXSExtension | WXSAttributeGroup
  WXSSchema _schema;
  
  
  WXSAttribute(final Element el, final Parent parent, final WXSSchema schema) {
    _parseAnnotation(el);
    for (Node n = el.firstChild; n != null; n=n.nextSibling) {
      if (n is Element && n.localName == "simpleType") {
        _simpleType = new WXSSimpleType(n as Element, null, schema);
        break;
      }
    }
    if (el.hasAttribute("name"))
      _name = el.getAttribute("name");
    if (el.hasAttribute("ref"))
      _ref = el.getAttribute("ref");
    if (el.hasAttribute("type"))
      _type = el.getAttribute("type");
    if (el.hasAttribute("use"))
      _use = el.getAttribute("use");
    if (el.hasAttribute("default"))
      _defaultAtt = el.getAttribute("default");
    if (el.hasAttribute("fixed"))
      _fixed = el.getAttribute("fixed");
    if (el.hasAttribute("form"))
      _form = el.getAttribute("form");
    
    _domElement = el;
    this._parent = parent;
    this._schema = schema;
  }
  
  void resolveReferences(final WXSSchema schema) {
    if (_simpleType != null)
      _simpleType.resolveReferences(schema, null);
    if (_ref != null) {
      final String prefixe = DaxeWXS._namePrefix(_ref);
      String tns;
      if (prefixe == "xml")
        tns = "http://www.w3.org/XML/1998/namespace";
      else
        tns = _domElement.lookupNamespaceURI(prefixe);
      _wxsRef = schema.resolveAttributeReference(DaxeWXS._localValue(_ref), tns);
      if (_wxsRef == null)
        print("WXSAttribute: Référence d'attribut introuvable : $_ref");
    }
    if (_simpleType == null && _type != null) {
      final String tns = _domElement.lookupNamespaceURI(DaxeWXS._namePrefix(_type));
      // pas de résolution pour les types du superschéma, sauf pour le superschéma
      if (tns == null || tns != _domElement.namespaceURI ||
          schema.getTargetNamespace() == null || schema.getTargetNamespace() == _domElement.namespaceURI) {
        final WXSType wxsType = schema.resolveTypeReference(DaxeWXS._localValue(_type), tns, null);
        if (wxsType is WXSSimpleType)
          _simpleType = wxsType;
      }
    }
    if (_simpleType == null && _wxsRef != null)
      _simpleType = _wxsRef._simpleType;
  }
  
  String getName() {
    if (_name == null && _wxsRef != null)
      return(_wxsRef.getName());
    return(_name);
  }
  
  String getUse() {
    return(_use);
  }
  
  String getForm() {
    return(_form);
  }
  
  Element getDOMElement() {
    return(_domElement);
  }
  
  String getNamespace() {
    if (_ref != null) {
      final String prefixe = DaxeWXS._namePrefix(_ref);
      if (prefixe != null) {
        final String ns = _domElement.lookupNamespaceURI(prefixe);
        if (ns != null)
          return(ns);
        if (prefixe == "xml")
          return("http://www.w3.org/XML/1998/namespace");
        return(null);
      }
    }
    bool qualified;
    if (_schema.getTopAttributes().contains(this))
      qualified = true;
    else if (_form != null)
      qualified = (_form == "qualified");
    else
      qualified = (_schema.getAttributeFormDefault() == "qualified");
    if (qualified) {
      final String tn = _schema.getTargetNamespace();
      if (tn == "")
        return(null);
      else
        return(tn);
    } else
      return(null);
  }
  
  WXSAttribute getWXSRef() {
    return(_wxsRef);
  }
  
  List<WXSElement> parentElements() {
    if (_parent != null)
      return(_parent.parentElements());
    return(new List<WXSElement>());
  }
  
  List<String> possibleValues() {
    if (_fixed != null) {
      final List<String> fixedval = new List<String>();
      fixedval.add(_fixed);
      return(fixedval);
    }
    if (_schema.getTargetNamespace() != null && _schema.getTargetNamespace() == _domElement.namespaceURI &&
        DaxeWXS._localValue(_type) == "bool")
      return(DaxeWXS._booleanValues(_type, _domElement)); // cas du superschéma
    if (_simpleType != null)
      return(_simpleType.possibleValues());
    else if (_type != null)
      return(DaxeWXS._booleanValues(_type, _domElement));
    return(null);
  }
  
  List<String> suggestedValues() {
    if (_fixed != null) {
      final List<String> fixedval = new List<String>();
      fixedval.add(_fixed);
      return(fixedval);
    }
    if (_schema.getTargetNamespace() != null && _schema.getTargetNamespace() == _domElement.namespaceURI &&
        DaxeWXS._localValue(_type) == "bool")
      return(DaxeWXS._booleanValues(_type, _domElement)); // cas du superschéma
    if (_simpleType != null)
      return(_simpleType.suggestedValues());
    else if (_type != null)
      return(DaxeWXS._booleanValues(_type, _domElement));
    return(null);
  }
  
  String defaultValue() {
    if (_defaultAtt != null)
      return(_defaultAtt);
    else if (_fixed != null)
      return(_fixed);
    else if (_wxsRef != null)
      return(_wxsRef.defaultValue());
    return(null);
  }
  
  bool validValue(final String value) {
    if (_fixed != null)
      return(_fixed == value);
    if  ((value == null || value == "") && _use == "required")
      return(false);
    if (_simpleType != null)
      return(_simpleType.validValue(value));
    if (_type != null) {
      final String tns = _domElement.lookupNamespaceURI(DaxeWXS._namePrefix(_type));
      if (tns != null && tns == _domElement.namespaceURI)
        return(WXSSimpleType.validateTypeValue(DaxeWXS._localValue(_type), value));
    }
    if (_wxsRef != null)
      return(_wxsRef.validValue(value));
    if (_type == null)
      return(true);
    return(false);
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_attribute_group.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_attribute_group.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;


class WXSAttributeGroup extends WXSAnnotated implements Parent {
  
  List<WXSThing> _attrDecls; // _attrDecls: (attribute|attributeGroup)*
  String _name = null;
  String _ref = null;
  
  WXSAttributeGroup _wxsRef = null;
  Element _domElement;
  Parent _parent; // WXSComplexType | WXSRestriction | WXSExtension | WXSAttributeGroup | WXSRedefine
  WXSSchema _schema;
  
  
  WXSAttributeGroup(final Element el, final Parent parent, final WXSSchema schema) {
    _parseAnnotation(el);
    _attrDecls = new List<WXSThing>();
    for (Node n = el.firstChild; n != null; n=n.nextSibling) {
      if (n is Element) {
        if (n.localName == "attribute")
          _attrDecls.add(new WXSAttribute(n as Element, this, schema));
        else if (n.localName == "attributeGroup")
          _attrDecls.add(new WXSAttributeGroup(n as Element, this, schema));
      }
    }
    if (el.hasAttribute("name"))
      _name = el.getAttribute("name");
    if (el.hasAttribute("ref"))
      _ref = el.getAttribute("ref");
    
    _domElement = el;
    this._parent = parent;
    this._schema = schema;
  }
  
  String getNamespace() {
    return(_schema.getTargetNamespace());
  }
  
  Parent getParent() {
    return(_parent);
  }
  
  void resolveReferences(final WXSSchema schema, final WXSThing redefine) {
    for (WXSThing attrDecl in _attrDecls) {
      if (attrDecl is WXSAttribute)
        attrDecl.resolveReferences(schema);
      else if (attrDecl is WXSAttributeGroup)
        attrDecl.resolveReferences(schema, redefine);
    }
    if (_ref != null) {
      final String prefixe = DaxeWXS._namePrefix(_ref);
      String tns;
      if (prefixe == "xml")
        tns = "http://www.w3.org/XML/1998/namespace";
      else
        tns = _domElement.lookupNamespaceURI(prefixe);
      _wxsRef = schema.resolveAttributeGroupReference(DaxeWXS._localValue(_ref), tns, redefine);
    }
  }
  
  String getName() {
    if (_name == null && _wxsRef != null)
      return(_wxsRef.getName());
    return(_name);
  }
  
  // from Parent
  List<WXSElement> parentElements() {
    if (_parent != null)
      return(_parent.parentElements());
    return(new List<WXSElement>());
  }
  
  List<WXSAttribute> attributes() {
    if (_wxsRef != null)
      return(_wxsRef.attributes());
    final List<WXSAttribute> liste = new List<WXSAttribute>();
    for (WXSThing attrDecl in _attrDecls) {
      if (attrDecl is WXSAttribute)
        liste.add(attrDecl);
      else if (attrDecl is WXSAttributeGroup)
        liste.addAll(attrDecl.attributes());
    }
    return(liste);
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_choice.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_choice.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;


class WXSChoice extends WXSExplicitGroup {
  
  WXSChoice(final Element el, final Parent parent, final WXSSchema schema) {
    _parse(el, parent, schema);
  }
  
  // from WithSubElements
  int validate(final List<WXSElement> subElements, final int start, final bool insertion) {
    int nb = 0;
    for (int i=start; i<subElements.length; ) {
      if (nb >= _maxOccurs)
        return(i);
      int pos = i;
      int max_pos = i;
      for (WithSubElements nestedParticle in _nestedParticles) {
        pos = nestedParticle.validate(subElements, i, insertion);
        if (insertion) {
          // elements b,a with content model (a,b)|(b,a) :
          // all particules have to be tried
          if (pos > max_pos)
            max_pos = pos;
        } else {
          if (pos > i)
            break;
        }
      }
      if (insertion)
        pos = max_pos;
      if (pos == i) {
        if (!insertion && nb < _minOccurs)
          return(start);
        return(i);
      }
      i = pos;
      nb++;
    }
    if (!insertion && nb < _minOccurs)
      return(start);
    return(subElements.length);
  }
  
  // from WithSubElements
  bool isOptionnal() {
    if (_nestedParticles.length == 0)
      return(true);
    if (_minOccurs == 0)
      return(true);
    for (WithSubElements nestedParticle in _nestedParticles) {
      if (nestedParticle.isOptionnal())
        return(true);
    }
    return(false);
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_complex_content.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_complex_content.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;



class WXSComplexContent extends WXSAnnotated implements WithSubElements, Parent {
  
  // (restriction|extension)
  WithSubElements _model = null; // WXSRestriction | WXSExtension
  bool _mixed = null;
  
  WXSComplexType _parent;
  
  
  WXSComplexContent(final Element el, final WXSComplexType parent, final WXSSchema schema) {
    _parseAnnotation(el);
    for (Node n = el.firstChild; n != null; n=n.nextSibling) {
      if (n is Element) {
        if (n.localName == "restriction")
          _model = new WXSRestriction(n as Element, this, schema);
        else if (n.localName == "extension")
          _model = new WXSExtension(n as Element, this, schema);
      }
    }
    if (el.hasAttribute("mixed"))
      _mixed = (el.getAttribute("mixed") == "true" || el.getAttribute("mixed") == "1");
    
    this._parent = parent;
  }
  
  // from WithSubElements
  void resolveReferences(final WXSSchema schema, final WXSThing redefine) {
    if (_model != null)
      _model.resolveReferences(schema, redefine);
  }
  
  // from WithSubElements
  List<WXSElement> allElements() {
    if (_model != null)
      return(_model.allElements());
    return(new List<WXSElement>());
  }
  
  // from WithSubElements
  List<WXSElement> subElements() {
    if (_model != null)
      return(_model.subElements());
    return(new List<WXSElement>());
  }
  
  // from WithSubElements
  String regularExpression() {
    if (_model != null)
      return(_model.regularExpression());
    return(null);
  }
  
  // from WithSubElements
  bool requiredChild(final WXSElement child) {
    if (_model != null)
      return(_model.requiredChild(child));
    return(null);
  }
  
  // from WithSubElements
  bool multipleChildren(final WXSElement child) {
    if (_model != null)
      return(_model.multipleChildren(child));
    return(null);
  }
  
  List<WXSAttribute> attributes() {
    if (_model is WXSRestriction)
      return((_model as WXSRestriction).attributes());
    else if (_model is WXSExtension)
      return((_model as WXSExtension).attributes());
    return(new List<WXSAttribute>());
  }
  
  // from Parent
  List<WXSElement> parentElements() {
    return(_parent.parentElements());
  }
  
  // from WithSubElements
  int validate(final List<WXSElement> subElements, final int start, final bool insertion) {
    if (_model != null)
      return(_model.validate(subElements, start, insertion));
    return(start);
  }
  
  // from WithSubElements
  bool isOptionnal() {
    if (_model != null)
      return(_model.isOptionnal());
    return(true);
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_complex_type.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_complex_type.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;


class WXSComplexType extends WXSAnnotated implements WXSType, WithSubElements, Parent {
  
  // (simpleContent | complexContent | ((group|all|choice|sequence)?, (attribute|attributeGroup)*))
  WXSSimpleContent _simpleContent = null;
  WithSubElements _model = null; // WXSComplexContent | WXSGroup | WXSAll | WXSChoice | WXSSequence
  List<WXSThing> _attrDecls; // attrDecls: (attribute|attributeGroup)*
  String _name = null;
  bool _mixed = false;
  bool _abstractAtt = false;
  
  Parent _parent; // WXSElement | WXSRedefine
  WXSSchema _schema;
  List<WXSElement> _references;
  List<WXSExtension> _extensions;
  
  
  WXSComplexType(final Element el, final Parent parent, final WXSSchema schema) {
    _parseAnnotation(el);
    _attrDecls = new List<WXSThing>();
    for (Node n = el.firstChild; n != null; n=n.nextSibling) {
      if (n is Element) {
        if (n.localName == "simpleContent")
          _simpleContent = new WXSSimpleContent(n as Element, schema);
        else if (n.localName == "complexContent")
          _model = new WXSComplexContent(n as Element, this, schema);
        else if (n.localName == "group")
          _model = new WXSGroup(n as Element, this, schema);
        else if (n.localName == "all")
          _model = new WXSAll(n as Element, this, schema);
        else if (n.localName == "choice")
          _model = new WXSChoice(n as Element, this, schema);
        else if (n.localName == "sequence")
          _model = new WXSSequence(n as Element, this, schema);
        else if (n.localName == "attribute")
          _attrDecls.add(new WXSAttribute(n as Element, this, schema));
        else if (n.localName == "attributeGroup")
          _attrDecls.add(new WXSAttributeGroup(n as Element, this, schema));
      }
    }
    if (el.hasAttribute("name"))
      _name = el.getAttribute("name");
    if (el.hasAttribute("mixed"))
      _mixed = el.getAttribute("mixed") == "true" || el.getAttribute("mixed") == "1";
    if (el.hasAttribute("abstract"))
      _abstractAtt = el.getAttribute("abstract") == "true" || el.getAttribute("abstract") == "1";
    
    this._parent = parent;
    this._schema = schema;
    _references = null;
    _extensions = null;
  }
  
  WXSSimpleContent getSimpleContent() {
    return(_simpleContent);
  }
  
  // from WXSType
  String getName() {
    return(_name);
  }
  
  bool getMixed() {
    return(_mixed);
  }
  
  // from WXSType
  String getNamespace() {
    return(_schema.getTargetNamespace());
  }
  
  // from WXSType
  Parent getParent() {
    return(_parent);
  }
  
  // from WithSubElements and WXSType
  void resolveReferences(final WXSSchema schema, final WXSThing redefine) {
    if (_simpleContent != null)
      _simpleContent.resolveReferences(schema, redefine);
    if (_model != null)
      _model.resolveReferences(schema, redefine);
    for (WXSThing attrDecl in _attrDecls) {
      if (attrDecl is WXSAttribute)
        attrDecl.resolveReferences(schema);
      else if (attrDecl is WXSAttributeGroup)
        attrDecl.resolveReferences(schema, redefine);
    }
  }
  
  void addReference(final WXSElement element) {
    if (_references == null)
      _references = new List<WXSElement>();
    _references.add(element);
  }
  
  void addExtension(final WXSExtension ext) {
    if (_extensions == null)
      _extensions = new List<WXSExtension>();
    _extensions.add(ext);
  }
  
  // from WithSubElements
  List<WXSElement> allElements() {
    if (_model != null)
      return(_model.allElements());
    return(new List<WXSElement>());
  }
  
  // from WithSubElements
  List<WXSElement> subElements() {
    final List<WXSElement> liste = new List<WXSElement>();
    if (_model != null)
      liste.addAll(_model.subElements());
    return(liste);
  }
  
  // from Parent
  List<WXSElement> parentElements() {
    final List<WXSElement> liste = new List<WXSElement>();
    if (_parent is WXSElement) {
      if (!(_parent as WXSElement).getAbstract())
        liste.add(_parent as WXSElement);
      final List<WXSElement> substitutions = (_parent as WXSElement).getSubstitutions();
      if (substitutions != null)
        liste.addAll(substitutions);
    }
    if (_references != null) {
      for (WXSElement el in _references) {
        if (!el.getAbstract())
          liste.add(el);
        final List<WXSElement> substitutions = el.getSubstitutions();
        if (substitutions != null)
          liste.addAll(substitutions);
      }
    }
    if (_extensions != null) {
      for (WXSExtension ext in _extensions)
        liste.addAll(ext.parentElements());
    }
    return(liste);
  }
  
  // from WithSubElements
  String regularExpression() {
    if (_model != null)
      return(_model.regularExpression());
    return(null);
  }
  
  // from WithSubElements
  bool requiredChild(final WXSElement child) {
    if (_model != null)
      return(_model.requiredChild(child));
    return(null);
  }
  
  // from WithSubElements
  bool multipleChildren(final WXSElement child) {
    if (_model != null)
      return(_model.multipleChildren(child));
    return(null);
  }
  
  // from WXSType
  List<String> possibleValues() {
    if (_simpleContent != null)
      return(_simpleContent.possibleValues());
    return(null);
  }
  
  // from WXSType
  List<String> suggestedValues() {
    if (_simpleContent != null)
      return(_simpleContent.suggestedValues());
    return(null);
  }
  
  List<WXSAttribute> attributes() {
    if (_simpleContent != null)
      return(_simpleContent.attributes());
    else if (_model is WXSComplexContent)
      return((_model as WXSComplexContent).attributes());
    final List<WXSAttribute> liste = new List<WXSAttribute>();
    for (WXSThing attrDecl in _attrDecls) {
      if (attrDecl is WXSAttribute)
        liste.add(attrDecl);
      else if (attrDecl is WXSAttributeGroup)
        liste.addAll(attrDecl.attributes());
    }
    return(liste);
  }
  
  // from WithSubElements
  int validate(final List<WXSElement> subElements, final int start, final bool insertion) {
    if (_simpleContent != null)
      return(start);
    else if (_model != null)
      return(_model.validate(subElements, start, insertion));
    return(start);
  }
  
  // from WithSubElements
  bool isOptionnal() {
    if (_simpleContent != null)
      return(true);
    else if (_model != null)
      return(_model.isOptionnal());
    return(true);
  }
  
  // from WXSType
  bool validValue(final String value) {
    if (_simpleContent != null)
      return(_simpleContent.validValue(value));
    // FIXME: we should check whether spaces matter or not here
    return(value.trim() == '' || containsText());
  }
  
  bool containsText() {
    // on teste le type parent quand un type est dérivé
    // par un autre qui ne redonne pas la liste des sous-éléments...
    // cf http://lists.w3.org/Archives/Public/xmlschema-dev/2005Sep/0025.html
    if (_model is WXSComplexContent) {
      WXSComplexContent cc = _model as WXSComplexContent;
      WXSType wxsBase = null;
      if (cc._model is WXSExtension) {
        WXSExtension ext = cc._model as WXSExtension;
        if (ext._model == null)
          wxsBase = ext._wxsBase;
      } else {
        WXSRestriction res = cc._model as WXSRestriction;
        if (res._model == null)
          wxsBase = res._wxsBase;
      }
      if (wxsBase is WXSComplexType)
        return((wxsBase as WXSComplexType).containsText());
    }
    if (getMixed())
      return(true);
    if (getSimpleContent() != null)
      return(true);
    return(false);
  }

}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_documentation.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_documentation.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;


class WXSDocumentation implements WXSThing {
  
  String _source = null; // URI
  String _lang = null; // xml:lang
  String _value = null;
  
  
  WXSDocumentation(final Element el) {
    if (el.hasAttribute("source"))
      _source = el.getAttribute("source");
    if (el.hasAttribute("xml:lang"))
      _lang = el.getAttribute("xml:lang");
    if (el.firstChild != null)
      _value = el.firstChild.nodeValue;
  }
  
  String getValue() {
    return(_value);
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_element.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_element.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;



class WXSElement extends WXSAnnotated implements WithSubElements, Parent {
  
  // (simpleType|complexType)?, (unique|key|keyref)*
  WXSSimpleType _simpleType = null;
  WXSComplexType _complexType = null;
  List<WXSThing> _identityConstraints; // (unique|key|keyref)*
  String _name = null;
  String _ref = null;
  String _type = null;
  String _substitutionGroup = null;
  int _minOccurs = 1;
  int _maxOccurs = 1;
  String _defaultAtt = null;
  String _fixed = null;
  bool _abstractAtt = false;
  String _form = null; // (qualified|unqualified)
  
  WXSElement _wxsRef = null;
  WXSElement _wxsSubstitutionGroup = null;
  Element _domElement;
  Parent _parent; // WXSAll | WXSChoice | WXSSequence
  WXSSchema _schema;
  List<WXSThing> _references; // WXSElement | WXSAny
  List<WXSElement> _substitutions;
  List<WXSElement> _correspondant; // corresponding elements cache
  List<WXSElement> _subElements; // sub-elements cache
  
  
  WXSElement(final Element el, final Parent parent, final WXSSchema schema) {
    _parseAnnotation(el);
    _identityConstraints = new List<WXSThing>();
    for (Node n = el.firstChild; n != null; n=n.nextSibling) {
      if (n is Element) {
        if (n.localName == "simpleType")
          _simpleType = new WXSSimpleType(n as Element, this, schema);
        else if (n.localName == "complexType")
          _complexType = new WXSComplexType(n as Element, this, schema);
        else if (n.localName == "unique")
          _identityConstraints.add(new WXSUnique(n as Element));
        else if (n.localName == "key")
          _identityConstraints.add(new WXSKey(n as Element));
        else if (n.localName == "keyref")
          _identityConstraints.add(new WXSKeyref(n as Element));
      }
    }
    if (el.hasAttribute("name"))
      _name = el.getAttribute("name");
    if (el.hasAttribute("ref"))
      _ref = el.getAttribute("ref");
    if (el.hasAttribute("type"))
      _type = el.getAttribute("type");
    if (el.hasAttribute("substitutionGroup"))
      _substitutionGroup = el.getAttribute("substitutionGroup");
    try {
      if (el.hasAttribute("minOccurs"))
        _minOccurs = int.parse(el.getAttribute("minOccurs"));
      if (el.hasAttribute("maxOccurs")) {
        if (el.getAttribute("maxOccurs") == "unbounded")
          _maxOccurs = 9007199254740992;
        else
          _maxOccurs = int.parse(el.getAttribute("maxOccurs"));
      }
    } on FormatException {
    }
    if (el.hasAttribute("default"))
      _defaultAtt = el.getAttribute("default");
    if (el.hasAttribute("fixed"))
      _fixed = el.getAttribute("fixed");
    if (el.hasAttribute("abstract"))
      _abstractAtt = el.getAttribute("abstract") == "true" || el.getAttribute("abstract") == "1";
    if (el.hasAttribute("form"))
      _form = el.getAttribute("form");
    
    _domElement = el;
    this._parent = parent;
    this._schema = schema;
    _references = null;
    _substitutions = null;
    _subElements = null;
    _correspondant = null;
  }
  
  String getName() {
    if (_name == null && _wxsRef != null)
      return(_wxsRef.getName());
    return(_name);
  }
  
  String getRef() {
    return(_ref);
  }
  
  int getMinOccurs() {
    return(_minOccurs);
  }
  
  int getMaxOccurs() {
    return(_maxOccurs);
  }
  
  bool getAbstract() {
    return(_abstractAtt);
  }
  
  
  Element getDOMElement() {
    return(_domElement);
  }
  
  String getNamespace() {
    bool qualified;
    if (_schema.getTopElements().contains(this))
      qualified = true;
    else if (_form != null)
      qualified = _form == "qualified";
    else
      qualified = _schema.getElementFormDefault() == "qualified";
    if (qualified)
      return(_schema.getTargetNamespace());
    else
      return(null);
  }
  
  Parent getParent() {
    return(_parent);
  }
  
  // from WithSubElements
  void resolveReferences(final WXSSchema schema, final WXSThing redefine) {
    if (_simpleType != null)
      _simpleType.resolveReferences(schema, null);
    if (_complexType != null)
      _complexType.resolveReferences(schema, redefine);
    if (_ref != null) {
      final String tns = _domElement.lookupNamespaceURI(DaxeWXS._namePrefix(_ref));
      _wxsRef = schema.resolveElementReference(DaxeWXS._localValue(_ref), tns);
      if (_wxsRef != null)
        _wxsRef.addReference(this);
      else
        print("Element reference not found : $_ref (namespace: $tns)");
    }
    if (_complexType == null && _simpleType == null && _type != null) {
      final String tns = _domElement.lookupNamespaceURI(DaxeWXS._namePrefix(_type));
      final WXSType wxsType = schema.resolveTypeReference(DaxeWXS._localValue(_type), tns, redefine);
      if (wxsType is WXSComplexType) {
        _complexType = wxsType;
        _complexType.addReference(this);
      } else if (wxsType is WXSSimpleType)
        _simpleType = wxsType;
    }
    if (_substitutionGroup != null) {
      final String tns = _domElement.lookupNamespaceURI(DaxeWXS._namePrefix(_substitutionGroup));
      _wxsSubstitutionGroup = schema.resolveElementReference(DaxeWXS._localValue(_substitutionGroup), tns);
      _wxsSubstitutionGroup.addSubstitution(this);
    }
  }
  
  void addReference(final WXSThing thing) {
    if (_references == null)
      _references = new List<WXSThing>();
    _references.add(thing);
  }
  
  void addSubstitution(final WXSElement el) {
    if (_substitutions == null)
      _substitutions = new List<WXSElement>();
    _substitutions.add(el);
  }
  
  List<WXSElement> getSubstitutions() {
    return(_substitutions);
  }
  
  // from WithSubElements
  List<WXSElement> allElements() {
    final List<WXSElement> liste = new List<WXSElement>();
    liste.add(this);
    if (_complexType != null)
      liste.addAll(_complexType.allElements());
    return(liste);
  }
  
  /**
   * Matching non-abstract named elements
   * (this element if it is named, named element if this is a reference, and substitutions).
   */
  List<WXSElement> matchingElements() {
    if (_correspondant != null)
      return(_correspondant);
    _correspondant = new List<WXSElement>();
    if (!_abstractAtt && _name != null)
      _correspondant.add(this);
    if (_wxsRef != null)
      _correspondant.addAll(_wxsRef.matchingElements());
    if (_substitutions != null)
      for (WXSElement substitution in _substitutions)
        _correspondant.addAll(substitution.matchingElements());
          return(_correspondant);
  }
  
  // from WithSubElements
  List<WXSElement> subElements() {
    if (_subElements != null)
      return(_subElements);
    final LinkedHashSet<WXSElement> set = new LinkedHashSet<WXSElement>();
    if (_wxsRef != null)
      set.addAll(_wxsRef.subElements());
    else if (_complexType != null)
      set.addAll(_complexType.subElements());
    else if (_simpleType == null && _type == null && _wxsSubstitutionGroup != null)
      set.addAll(_wxsSubstitutionGroup.subElements());
    _subElements = new List.from(set);
    return(_subElements);
  }
  
  // from Parent
  List<WXSElement> parentElements() {
    final LinkedHashSet<WXSElement> set = new LinkedHashSet<WXSElement>();
    if (_parent != null)
      set.addAll(_parent.parentElements());
    if (_references != null) {
      for (WXSThing reference in _references) {
        if (reference is WXSElement)
          set.addAll(reference.parentElements());
        else if (reference is WXSAny)
          set.addAll(reference.parentElements());
      }
    }
    if (_wxsSubstitutionGroup != null)
      set.addAll(_wxsSubstitutionGroup.parentElements());
    return(new List.from(set));
  }
  
  /**
   * Regular expression for the user interface (with the element titles: cannot be used for validation)
   */
  String elementRegularExpression() {
    // this element is assumed to have a name
    if (_complexType == null && _simpleType == null && _type == null && _wxsSubstitutionGroup != null)
      return(_wxsSubstitutionGroup.elementRegularExpression());
    if (_complexType == null)
      return(null);
    return(_complexType.regularExpression());
  }
  
  /**
   * Regular expression for this element as a model sub-element.
   * Returns null if there is no non-abstract element matching this element.
   */
  // from WithSubElements
  String regularExpression() {
    final List<WXSElement> liste = matchingElements();
    if (liste.length == 0)
      return(null);
    final StringBuffer sb = new StringBuffer();
    if (liste.length > 1)
      sb.write('(');
      for (int i=0; i<liste.length; i++) {
        final WXSElement el = liste[i];
        sb.write(_schema.elementTitle(el));
        if (i != liste.length - 1)
          sb.write('|');
      }
      if (liste.length > 1)
        sb.write(')');
      if (_minOccurs == 0 && _maxOccurs == 1)
        sb.write('?');
      else if (_minOccurs == 0 && _maxOccurs > 1)
        sb.write('*');
      else if (_minOccurs > 0 && _maxOccurs > 1)
        sb.write('+');
      return(sb.toString());
  }
  
  // from WithSubElements
  bool requiredChild(final WXSElement child) {
    // this element is assumed to have a name
    if (_complexType == null && _simpleType == null && _type == null && _wxsSubstitutionGroup != null)
      return(_wxsSubstitutionGroup.requiredChild(child));
    if (_complexType == null)
      return(null);
    return(_complexType.requiredChild(child));
  }
  
  // from WithSubElements
  bool multipleChildren(final WXSElement child) {
    // this element is assumed to have a name
    if (_complexType == null && _simpleType == null && _type == null && _wxsSubstitutionGroup != null)
      return(_wxsSubstitutionGroup.multipleChildren(child));
    if (_complexType == null)
      return(null);
    return(_complexType.multipleChildren(child));
  }
  
  List<String> possibleValues() {
    if (_fixed != null) {
      final List<String> fixedval = new List<String>();
      fixedval.add(_fixed);
      return(fixedval);
    }
    if (_simpleType != null)
      return(_simpleType.possibleValues());
    else if (_complexType != null)
      return(_complexType.possibleValues());
    else if (_type != null)
      return(DaxeWXS._booleanValues(_type, _domElement));
    else if (_simpleType == null && _wxsSubstitutionGroup != null)
      return(_wxsSubstitutionGroup.possibleValues());
    return(null);
  }
  
  List<String> suggestedValues() {
    if (_fixed != null) {
      final List<String> fixedval = new List<String>();
      fixedval.add(_fixed);
      return(fixedval);
    }
    if (_simpleType != null)
      return(_simpleType.suggestedValues());
    else if (_complexType != null)
      return(_complexType.suggestedValues());
    else if (_type != null)
      return(DaxeWXS._booleanValues(_type, _domElement));
    else if (_simpleType == null && _wxsSubstitutionGroup != null)
      return(_wxsSubstitutionGroup.suggestedValues());
    return(null);
  }
  
  List<WXSAttribute> attributes() {
    if (_wxsRef != null)
      return(_wxsRef.attributes());
    if (_complexType != null)
      return(_complexType.attributes());
    else if (_simpleType == null && _type == null && _wxsSubstitutionGroup != null)
      return(_wxsSubstitutionGroup.attributes());
    return(new List<WXSAttribute>());
  }
  
  bool containsText() {
    if (_type != null) {
      final String tns = _domElement.lookupNamespaceURI(DaxeWXS._namePrefix(_type));
      final String schemaNamespace = _domElement.namespaceURI;
      // if the type is from XML schemas (like "string" or "anyURI")
      // it is considered to be text (except if the schema is the superschema)
      if (schemaNamespace != _schema.getTargetNamespace() && schemaNamespace == tns)
        return(true);
    }
    if (_complexType != null)
      return(_complexType.containsText());
    if (_simpleType != null)
      return(true);
    if (_complexType == null && _type == null && _wxsSubstitutionGroup != null)
      return(_wxsSubstitutionGroup.containsText());
    return(false);
  }
  
  /**
   * Validation of a named element.
   * Returns true if the given list of sub-elements is a valid set of sub-elements.
   * If insert is true, all the sub-elements are optionnal.
   */
  bool validateSubElements(final List<WXSElement> subElements, final bool insert) {
    if (_complexType == null) {
      if (_simpleType == null && _type == null && _wxsSubstitutionGroup != null)
        return(_wxsSubstitutionGroup.validateSubElements(subElements, insert));
      return(subElements.length == 0);
    }
    if (subElements.length == 0) {
      if (insert)
        return(true);
      if (_complexType.isOptionnal())
        return(true);
    }
    final int pos = _complexType.validate(subElements, 0, insert);
    return(pos > 0 && pos == subElements.length);
  }
  
  /**
   * Validation of a sub-element.
   * Returns the position in the list as far as validation is possible
   * (start if no validation is possible, subElements.length if everything is validated).
   */
  // from WithSubElements
  int validate(final List<WXSElement> subElements, final int start, final bool insertion) {
    int nb = 0;
    final List<WXSElement> correspondant = matchingElements();
    for (int i=start; i<subElements.length; i++) {
      if (nb >= _maxOccurs)
        return(i);
      bool found = false;
      for (WXSElement el in correspondant)
        if (el == subElements[i])
          found = true;
          if (!found) {
            if (!insertion && nb < _minOccurs)
              return(start);
            return(i);
          }
          nb++;
    }
    if (!insertion && nb < _minOccurs)
      return(start);
    return(start + nb);
  }
  
  // from WithSubElements
  bool isOptionnal() {
    return(_minOccurs == 0);
  }
  
  bool validValue(final String value) {
    if (_fixed != null)
      return(_fixed == value);
    if (_simpleType != null)
      return(_simpleType.validValue(value));
    if (_complexType != null)
      return(_complexType.validValue(value));
    if (_type != null) {
      final String tns = _domElement.lookupNamespaceURI(DaxeWXS._namePrefix(_type));
      if (tns != null && tns == _domElement.namespaceURI)
        return(WXSSimpleType.validateTypeValue(DaxeWXS._localValue(_type), value));
      return(false);
    } else
      return(true);
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_exception.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_exception.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;

class WXSException implements Exception {
  final String message;
  final Exception parentException;
  
  const WXSException([this.message, this.parentException]);
  
  String toString() {
    String s;
    if (message == null)
      s = 'WXSException';
    else
      s = message;
    if (parentException != null)
      s = "$s (parent exception: $parentException)";
    return(s);
  }
  
}


Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_explicit_group.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_explicit_group.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;


/**
 * WXSChoice and WXSSequence derive from this class.
 */
abstract class WXSExplicitGroup extends WXSAnnotated implements WithSubElements, Parent {
  
  List<WithSubElements> _nestedParticles; // (element, group, choice, sequence, any)*
  int _minOccurs = 1;
  int _maxOccurs = 1;
  
  Parent _parent; // WXSComplexType | WXSRestriction | WXSExtension | WXSGroup | WXSExplicitGroup
  
  
  void _parse(final Element el, final Parent parent, final WXSSchema schema) {
    _parseAnnotation(el);
    _nestedParticles = new List<WithSubElements>();
    for (Node n = el.firstChild; n != null; n=n.nextSibling) {
      if (n is Element) {
        if (n.localName == "element")
          _nestedParticles.add(new WXSElement(n as Element, this, schema));
        else if (n.localName == "group")
          _nestedParticles.add(new WXSGroup(n as Element, this, schema));
        else if (n.localName == "choice")
          _nestedParticles.add(new WXSChoice(n as Element, this, schema));
        else if (n.localName == "sequence")
          _nestedParticles.add(new WXSSequence(n as Element, this, schema));
        else if (n.localName == "any")
          _nestedParticles.add(new WXSAny(n as Element, this, schema));
      }
    }
    try {
      if (el.hasAttribute("minOccurs"))
        _minOccurs = int.parse(el.getAttribute("minOccurs"));
      if (el.hasAttribute("maxOccurs")) {
        if (el.getAttribute("maxOccurs") == "unbounded")
          _maxOccurs = 9007199254740992;
        else
          _maxOccurs = int.parse(el.getAttribute("maxOccurs"));
      }
    } on FormatException {
    }
    
    this._parent = parent;
  }
  
  // from WithSubElements
  void resolveReferences(final WXSSchema schema, final WXSThing redefine) {
    for (WithSubElements nestedParticle in _nestedParticles)
      if (!(nestedParticle is WXSAny))
        nestedParticle.resolveReferences(schema, redefine);
  }
  
  // from WithSubElements
  List<WXSElement> allElements() {
    final List<WXSElement> liste = new List<WXSElement>();
    for (WithSubElements nestedParticle in _nestedParticles)
      liste.addAll(nestedParticle.allElements());
    return(liste);
  }
  
  // from WithSubElements
  List<WXSElement> subElements() {
    final List<WXSElement> liste = new List<WXSElement>();
    for (WithSubElements nestedParticle in _nestedParticles) {
      if (nestedParticle is WXSElement)
        liste.addAll(nestedParticle.matchingElements());
      else
        liste.addAll(nestedParticle.subElements());
    }
    return(liste);
  }
  
  // from Parent
  List<WXSElement> parentElements() {
    if (_parent != null)
      return(_parent.parentElements());
    return(new List<WXSElement>());
  }
  
  // from WithSubElements
  String regularExpression() {
    if (_nestedParticles.length == 0)
      return(null);
    final String separateur = (this is WXSChoice) ? "|" : ", ";
    StringBuffer sb = new StringBuffer();
    if (_nestedParticles.length > 1 || _minOccurs != 1 || _maxOccurs != 1)
      sb.write('(');
    bool premier = true;
    for (WithSubElements nestedParticle in _nestedParticles) {
      final String er = nestedParticle.regularExpression();
      if (er != null) {
        if (!premier)
          sb.write(separateur);
        premier = false;
        sb.write(er);
      }
    }
    if (_nestedParticles.length > 1 || _minOccurs != 1 || _maxOccurs != 1)
      sb.write(')');
    if (_nestedParticles.length == 1 && sb.length > 2) {
      String ssb = sb.toString();
      if (ssb.substring(0, 2) == "((" && ssb.substring(sb.length - 2, sb.length) == "))") {
        sb = new StringBuffer(ssb.substring(1, ssb.length - 1));
      }
    }
    if (_minOccurs == 0 && _maxOccurs == 1)
      sb.write('?');
    else if (_minOccurs == 0 && _maxOccurs > 1)
      sb.write('*');
    else if (_minOccurs > 0 && _maxOccurs > 1)
      sb.write('+');
    return(sb.toString());
  }
  
  // from WithSubElements
  bool requiredChild(final WXSElement child) {
    for (WithSubElements nestedParticle in _nestedParticles) {
      if (nestedParticle is WXSElement) {
        for (WXSElement el in nestedParticle.matchingElements())
          if (el == child)
            return((this is WXSSequence || _nestedParticles.length == 1) &&
                _minOccurs != 0 &&
                nestedParticle.getMinOccurs() != 0);
      } else  {
        bool bb = nestedParticle.requiredChild(child);
        if (bb != null)
          return(bb);
      }
    }
    return(null);
  }
  
  // from WithSubElements
  bool multipleChildren(final WXSElement child) {
    for (WithSubElements nestedParticle in _nestedParticles) {
      if (nestedParticle is WXSElement) {
        for (WXSElement el in nestedParticle.matchingElements())
          if (el == child)
            return(nestedParticle.getMaxOccurs() > 1 || _maxOccurs > 1);
      } else  {
        bool bb = nestedParticle.multipleChildren(child);
        if (bb != null && !bb && _maxOccurs > 1)
          bb = true;
        if (bb != null)
          return(bb);
      }
    }
    return(null);
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_extension.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_extension.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;


class WXSExtension extends WXSAnnotated implements WithSubElements, Parent {
  
  // (group|all|choice|sequence)?, (attribute|attributeGroup)*
  WithSubElements _model = null; // WXSGroup | WXSAll | WXSChoice | WXSSequence
  List<WXSThing> _attrDecls; // attrDecls: (attribute|attributeGroup)*
  String _base = null;
  WXSType _wxsBase = null;
  
  Element _domElement;
  WXSComplexContent _parent;
  
  
  WXSExtension(final Element el, final WXSComplexContent parent, final WXSSchema schema) {
    _parseAnnotation(el);
    _attrDecls = new List<WXSThing>();
    for (Node n = el.firstChild; n != null; n=n.nextSibling) {
      if (n is Element) {
        if (n.localName == "group")
          _model = new WXSGroup(n as Element, this, schema);
        else if (n.localName == "all")
          _model = new WXSAll(n as Element, this, schema);
        else if (n.localName == "choice")
          _model = new WXSChoice(n as Element, this, schema);
        else if (n.localName == "sequence")
          _model = new WXSSequence(n as Element, this, schema);
        else if (n.localName == "attribute")
          _attrDecls.add(new WXSAttribute(n as Element, this, schema));
        else if (n.localName == "attributeGroup")
          _attrDecls.add(new WXSAttributeGroup(n as Element, this, schema));
      }
    }
    if (el.hasAttribute("base"))
      _base = el.getAttribute("base");
    
    _domElement = el;
    this._parent = parent;
  }
  
  // from WithSubElements
  void resolveReferences(final WXSSchema schema, final WXSThing redefine) {
    if (_model != null)
      _model.resolveReferences(schema, redefine);
    for (WXSThing attrDecl in _attrDecls) {
      if (attrDecl is WXSAttribute)
        attrDecl.resolveReferences(schema);
      else if (attrDecl is WXSAttributeGroup)
        attrDecl.resolveReferences(schema, redefine);
    }
    if (_base != null) {
      final String tns = _domElement.lookupNamespaceURI(DaxeWXS._namePrefix(_base));
      _wxsBase = schema.resolveTypeReference(DaxeWXS._localValue(_base), tns, redefine);
      if (_wxsBase is WXSComplexType)
        (_wxsBase as WXSComplexType).addExtension(this);
    }
  }
  
  // from WithSubElements
  List<WXSElement> allElements() {
    final List<WXSElement> liste = new List<WXSElement>();
    if (_model != null)
      liste.addAll(_model.allElements());
    return(liste);
  }
  
  // from WithSubElements
  List<WXSElement> subElements() {
    final List<WXSElement> liste = new List<WXSElement>();
    if (_wxsBase is WXSComplexType)
      liste.addAll((_wxsBase as WXSComplexType).subElements());
    if (_model != null)
      liste.addAll(_model.subElements());
    return(liste);
  }
  
  // from Parent
  List<WXSElement> parentElements() {
    if (_parent != null)
      return(_parent.parentElements());
    else
      return(new List<WXSElement>());
  }
  
  // from WithSubElements
  String regularExpression() {
    String erBase;
    if (_wxsBase is WXSComplexType)
      erBase = (_wxsBase as WXSComplexType).regularExpression();
    else
      erBase = null;
    String erModele;
    if (_model != null)
      erModele = _model.regularExpression();
    else
      erModele = null;
    if (erBase == null && erModele == null)
      return("");
    else if (erBase != null && erModele == null)
      return(erBase);
    else if (erBase == null && erModele != null)
      return(erModele);
    else
      return("($erBase, $erModele)");
  }
  
  // from WithSubElements
  bool requiredChild(final WXSElement child) {
    bool bb1 = null;
    if (_wxsBase is WXSComplexType)
      bb1 = (_wxsBase as WXSComplexType).requiredChild(child);
    if (bb1 != null && bb1)
      return(bb1);
    bool bb2 = null;
    if (_model != null)
      bb2 = _model.requiredChild(child);
    if (bb2 != null && bb2)
      return(bb2);
    return(bb1 != null ? bb1 : bb2);
  }
  
  // from WithSubElements
  bool multipleChildren(final WXSElement child) {
    bool bb1 = null;
    if (_wxsBase is WXSComplexType)
      bb1 = (_wxsBase as WXSComplexType).multipleChildren(child);
    if (bb1 != null && bb1)
      return(bb1);
    bool bb2 = null;
    if (_model != null)
      bb2 = _model.multipleChildren(child);
    return(bb1 != null ? bb1 : bb2);
  }
  
  List<String> possibleValues() {
    if (_wxsBase != null)
      return(_wxsBase.possibleValues());
    else if (_base != null)
      return(DaxeWXS._booleanValues(_base, _domElement));
    return(null);
  }
  
  List<String> suggestedValues() {
    if (_wxsBase != null)
      return(_wxsBase.suggestedValues());
    else if (_base != null)
      return(DaxeWXS._booleanValues(_base, _domElement));
    return(null);
  }
  
  List<WXSAttribute> attributes() {
    final List<WXSAttribute> liste = new List<WXSAttribute>();
    for (WXSThing attrDecl in _attrDecls) {
      if (attrDecl is WXSAttribute)
        liste.add(attrDecl);
      else if (attrDecl is WXSAttributeGroup)
        liste.addAll(attrDecl.attributes());
    }
    if (_wxsBase is WXSComplexType) {
      final List<WXSAttribute> listeBase = (_wxsBase as WXSComplexType).attributes();
      final List<WXSAttribute> aAjouter = new List<WXSAttribute>();
      for (WXSAttribute attributExt in liste) {
        final String nomExt = attributExt.getName();
        bool trouve = false;
        for (WXSAttribute attributBase in listeBase)
          if (nomExt == attributBase.getName()) {
            trouve = true;
            break;
          }
        if (!trouve)
          aAjouter.add(attributExt);
      }
      listeBase.addAll(aAjouter);
      return(listeBase);
    }
    return(liste);
  }
  
  // from WithSubElements
  int validate(final List<WXSElement> subElements, final int start, final bool insertion) {
    int pos = start;
    if (_wxsBase is WXSComplexType) {
      pos = (_wxsBase as WXSComplexType).validate(subElements, start, insertion);
      if (pos == start && !insertion && !(_wxsBase as WXSComplexType).isOptionnal())
        return(start);
    }
    if (_model != null) {
      int pos2 = _model.validate(subElements, pos, insertion);
      if (pos2 == pos && !insertion && !_model.isOptionnal())
        return(start);
      pos = pos2;
    }
    return(pos);
  }
  
  // from WithSubElements
  bool isOptionnal() {
    if (_wxsBase is WXSComplexType && !(_wxsBase as WXSComplexType).isOptionnal())
      return(false);
    if (_model != null)
      return(_model.isOptionnal());
    return(true);
  }
  
  bool validValue(final String value) {
    if (_wxsBase != null)
      return(_wxsBase.validValue(value));
    else if (_base != null)
      return(WXSSimpleType.validateTypeValue(DaxeWXS._localValue(_base), value));
    return(false);
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_facet.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_facet.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;



class WXSFacet extends WXSAnnotated {
  
  String _facet; // (minExclusive|minInclusive|maxExclusive|maxInclusive|totalDigits|fractionDigits|length|minLength|maxLength|enumeration|pattern)
  // TODO: add whiteSpace (before pattern)
  String _value = null;
  bool _fixed = false;
  
  int _iparam = 0;
  
  
  WXSFacet(final Element el) {
    _parseAnnotation(el);
    _facet = el.localName;
    if (el.hasAttribute("value")) {
      _value = el.getAttribute("value");
      _iparam = int.parse(_value, onError:(String source) => 0);
      if (_facet == "pattern") {
        // approximative replacements of \i, \I, \c and \C
        _value = _value.replaceAll("\\i", "[^<>&#!/?'\",0-9.\\-\\s]");
        _value = _value.replaceAll("\\I", "[^a-zA-Z]");
        _value = _value.replaceAll("\\c", "[^<>&#!/?'\",\\s]");
        _value = _value.replaceAll("\\C", "\\W");
        // replacement of '$' into '\$'
        _value = _value.replaceAll("\$", "\\\$");
        // replacement of '^' into '\^' except after '[' (but not '\[')
        for (int i=0; i<_value.length; i++) {
          if (_value[i] == '^' &&
              (i == 0 ||
              (_value[i-1] != '[' ||
              (i > 1 && _value[i-2] == '\\')))) {
            _value = _value.substring(0, i) + "\\^" + _value.substring(i+1);
            i++;
          }
        }
      }
    }
    if (el.hasAttribute("fixed"))
      _fixed = el.getAttribute("fixed") == "true" || el.getAttribute("fixed") == "1";
  }
  
  String getFacet() {
    return(_facet);
  }
  
  String getValue() {
    return(_value);
  }
  
  bool validValue(final String value) {
    if (_facet == "minExclusive") {
      try {
        final double val = double.parse(value);
        return(val > _iparam);
      } on FormatException {
        return(false);
      }
    } else if (_facet == "minInclusive") {
      try {
        final double val = double.parse(value);
        return(val >= _iparam);
      } on FormatException {
        return(false);
      }
    } else if (_facet == "maxExclusive") {
      try {
        final double val = double.parse(value);
        return(val < _iparam);
      } on FormatException {
        return(false);
      }
    } else if (_facet == "maxInclusive") {
      try {
        final double val = double.parse(value);
        return(val <= _iparam);
      } on FormatException {
        return(false);
      }
    } else if (_facet == "totalDigits") {
      int nb = 0;
      for (int i=0; i<value.length; i++)
        if (value[i].compareTo('0') >= 0 && value[i].compareTo('9') <= 0)
          nb++;
      return(nb <= _iparam);
    } else if (_facet =="fractionDigits") {
      int nb = 0;
      bool apres = false;
      for (int i=0; i<value.length; i++) {
        if (!apres) {
          if (value[i] == '.')
            apres = true;
        } else if (value[i].compareTo('0') >= 0 && value[i].compareTo('9') <= 0)
          nb++;
      }
      return(nb <= _iparam);
    } else if (_facet == "length")
      return(value.length == _iparam);
    else if (_facet == "minLength")
      return(value.length >= _iparam);
    else if (_facet == "maxLength")
      return(value.length <= _iparam);
    else if (_facet == "enumeration") {
      return(_value != null && _value == value);
      // TODO: enumeration based on integers, e.g. 02 valid for 2
    } else if (_facet == "whiteSpace") {
      return(true);
      /*
      ?!?
      if ("collapse".equals(_value))
      return(!"replace".equals(valeur) && !"preserve".equals(valeur));
      else if ("replace".equals(_value))
      return(!"preserve".equals(valeur));
      else
      return(true);
       */
    } else if (_facet == "pattern") {
      return(WXSSimpleType._verifExpr(value, _value));
    } else
      return(true);
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_field.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_field.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;


class WXSField extends WXSAnnotated {
  
  String _xpath = null;
  
  
  WXSField(final Element el) {
    if (el.hasAttribute("xpath"))
      _xpath = el.getAttribute("xpath");
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_group.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_group.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;


class WXSGroup extends WXSAnnotated implements WithSubElements, Parent {
  
  // if not ref: (all|choice|sequence)
  WithSubElements _modele = null; // (WXSAll | WXSChoice | WXSSequence)
  String _name = null;
  String _ref = null;
  WXSGroup _wxsRef = null;
  int _minOccurs = 1;
  int _maxOccurs = 1;
  
  Element _domElement;
  Parent _parent; // WXSComplexType | WXSRestriction | WXSExtension | WXSExplicitGroup | WXSRedefine
  WXSSchema _schema;
  List<WXSGroup> _references;
  
  
  WXSGroup(final Element el, final Parent parent, final WXSSchema schema) {
    _parseAnnotation(el);
    for (Node n = el.firstChild; n != null; n=n.nextSibling) {
      if (n is Element) {
        if (n.localName == "all")
          _modele = new WXSAll(n as Element, this, schema);
        else if (n.localName == "choice")
          _modele = new WXSChoice(n as Element, this, schema);
        else if (n.localName == "sequence")
          _modele = new WXSSequence(n as Element, this, schema);
      }
    }
    if (el.hasAttribute("name"))
      _name = el.getAttribute("name");
    if (el.hasAttribute("ref"))
      _ref = el.getAttribute("ref");
    try {
      if (el.hasAttribute("minOccurs"))
        _minOccurs = int.parse(el.getAttribute("minOccurs"));
      if (el.hasAttribute("maxOccurs")) {
        if (el.getAttribute("maxOccurs") == "unbounded")
          _maxOccurs = 9007199254740992;
        else
          _maxOccurs = int.parse(el.getAttribute("maxOccurs"));
      }
    } on FormatException {
    }
    
    _domElement = el;
    this._parent = parent;
    this._schema = schema;
    _references = null;
  }
  
  String getName() {
    if (_name == null && _wxsRef != null)
      return(_wxsRef.getName());
    return(_name);
  }
  
  String getNamespace() {
    return(_schema.getTargetNamespace());
  }
  
  Parent getParent() {
    return(_parent);
  }
  
  // from WithSubElements
  void resolveReferences(final WXSSchema schema, final WXSThing redefine) {
    if (_modele != null)
      _modele.resolveReferences(schema, redefine);
    if (_ref != null) {
      final String tns = _domElement.lookupNamespaceURI(DaxeWXS._namePrefix(_ref));
      _wxsRef = schema.resolveGroupReference(DaxeWXS._localValue(_ref), tns, redefine);
      if (_wxsRef != null)
        _wxsRef.addReference(this);
      else
        print("Référence de groupe introuvable : $_ref");
    }
  }
  
  void addReference(final WXSGroup group) {
    if (_references == null)
      _references = new List<WXSGroup>();
    _references.add(group);
  }
  
  // from WithSubElements
  List<WXSElement> allElements() {
    if (_modele != null)
      return(_modele.allElements());
    return(new List<WXSElement>());
  }
  
  // from WithSubElements
  List<WXSElement> subElements() {
    if (_wxsRef != null)
      return(_wxsRef.subElements());
    if (_modele != null)
      return(_modele.subElements());
    return(new List<WXSElement>());
  }
  
  // from Parent
  List<WXSElement> parentElements() {
    final List<WXSElement> liste = new List<WXSElement>();
    if (_parent != null)
      liste.addAll(_parent.parentElements());
    if (_references != null) {
      for (WXSGroup groupe in _references)
        liste.addAll(groupe.parentElements());
    }
    return(liste);
  }
  
  // from WithSubElements
  String regularExpression() {
    String er;
    if (_wxsRef != null)
      er = _wxsRef.regularExpression();
    else if (_modele != null)
      er = _modele.regularExpression();
    else
      er = "()";
    if (_minOccurs == 0 && _maxOccurs == 1)
      return("$er?");
    else if (_minOccurs == 0 && _maxOccurs > 1)
      return("$er*");
    else if (_minOccurs > 0 && _maxOccurs > 1)
      return("$er+");
    else
      return(er);
  }
  
  // from WithSubElements
  bool requiredChild(final WXSElement child) {
    if (_wxsRef != null)
      return(_wxsRef.requiredChild(child));
    // renvoie null si l'enfant n'en est pas un
    bool bb = null;
    if (_modele != null)
      bb = _modele.requiredChild(child);
    return(bb);
  }
  
  // from WithSubElements
  bool multipleChildren(final WXSElement child) {
    if (_wxsRef != null)
      return(_wxsRef.multipleChildren(child));
    // renvoie null si l'enfant n'en est pas un
    bool bb = null;
    if (_modele != null)
      bb = _modele.multipleChildren(child);
    return(bb);
  }
  
  // from WithSubElements
  int validate(final List<WXSElement> subElements, final int start, final bool insertion) {
    if (!insertion && subElements.length < _minOccurs)
      return(start);
    int nb = 0;
    for (int i=start; i<subElements.length; ) {
      if (nb >= _maxOccurs)
        return(i);
      int pos = i;
      if (_wxsRef != null)
        pos = _wxsRef.validate(subElements, i, insertion);
      else if (_modele != null)
        pos = _modele.validate(subElements, i, insertion);
      if (pos == i)
        return(i);
      i = pos;
      nb++;
    }
    return(subElements.length);
  }
  
  // from WithSubElements
  bool isOptionnal() {
    if (_minOccurs == 0)
      return(true);
    if (_wxsRef != null)
      return(_wxsRef.isOptionnal());
    if (_modele != null)
      return(_modele.isOptionnal());
    return(true);
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_import.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_import.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;



class WXSImport extends WXSAnnotated {
  
  String _namespace = null; // URI
  String _schemaLocation = null; // URI
  
  WXSSchema _schemaInclu = null;
  
  
  WXSImport(final Element el, final WXSSchema schema) {
    if (el.hasAttribute("namespace"))
      _namespace = el.getAttribute("namespace");
    if (el.hasAttribute("schemaLocation"))
      _schemaLocation = el.getAttribute("schemaLocation");
  }
  
  Future _inclusions(final WXSSchema schema) { // can throw a WXSException
    if (_schemaLocation == null)
      return(new Future.value(null));
    Completer completer = new Completer();
    schema.newIncludedSchema(_schemaLocation, _namespace, schema).then((WXSSchema schema) {
      _schemaInclu = schema;
      completer.complete();
    }, onError: (WXSException ex) {
      completer.completeError(ex);
    });
    return(completer.future);
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_include.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_include.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;


class WXSInclude extends WXSAnnotated {
  
  String _schemaLocation = null; // URI
  
  WXSSchema _schemaInclu = null;
  
  
  WXSInclude(final Element el, final WXSSchema schema) {
    _parseAnnotation(el);
    if (el.hasAttribute("schemaLocation"))
      _schemaLocation = el.getAttribute("schemaLocation");
  }
  
  Future _inclusions(final WXSSchema schema) { // can throw a WXSException
    Completer completer = new Completer();
    schema.newIncludedSchema(_schemaLocation, null, schema).then((WXSSchema schema) {
      _schemaInclu = schema;
      completer.complete();
    }, onError: (WXSException ex) {
      completer.completeError(ex);
    });
    return(completer.future);
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_key.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_key.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;


class WXSKey extends WXSKeybase {
  
  WXSKey(final Element el) {
    _parse(el);
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_keybase.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_keybase.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;


abstract class WXSKeybase extends WXSAnnotated {
  
  // (selector, field+)
  WXSSelector _selector = null;
  List<WXSField> _fields;
  String _name = null;
  
  
  void _parse(final Element el) {
    _parseAnnotation(el);
    _fields = new List<WXSField>();
    for (Node n = el.firstChild; n != null; n=n.nextSibling) {
      if (n is Element) {
        if (n.localName == "selector")
          _selector = new WXSSelector(n as Element);
        else if (n.localName == "field")
          _fields.add(new WXSField(n as Element));
      }
    }
    if (el.hasAttribute("name"))
      _name = el.getAttribute("name");
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_keyref.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_keyref.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;


class WXSKeyref extends WXSKeybase {
  
  String _refer = null;
  
  
  WXSKeyref(final Element el) {
    _parse(el);
    
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_list.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_list.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;



class WXSList extends WXSAnnotated {
  
  WXSSimpleType _simpleType = null;
  String _itemType = null;
  
  Element _domElement;
  
  
  WXSList(final Element el, final WXSSchema schema) {
    _parseAnnotation(el);
    for (Node n = el.firstChild; n != null; n=n.nextSibling) {
      if (n is Element && n.localName == "simpleType") {
        _simpleType = new WXSSimpleType(n as Element, null, schema);
        break;
      }
    }
    if (el.hasAttribute("itemType"))
      _itemType = el.getAttribute("itemType");
    
    _domElement = el;
  }
  
  void resolveReferences(final WXSSchema schema, final WXSThing redefine) {
    if (_simpleType != null)
      _simpleType.resolveReferences(schema, redefine);
    if (_itemType != null && _simpleType == null) {
      final String tns = _domElement.lookupNamespaceURI(DaxeWXS._namePrefix(_itemType));
      final WXSType wxsType = schema.resolveTypeReference(DaxeWXS._localValue(_itemType), tns, redefine);
      if (wxsType is WXSSimpleType)
        _simpleType = wxsType;
      else {
        final String espaceSchema = _domElement.namespaceURI;
        if (espaceSchema != tns)
          _itemType = null; // si le type n'a pas été résolu il doit être un type des schémas XML
      }
    }
  }
  
  bool validValue(final String value) {
    if (_simpleType == null && _itemType == null)
      return(false);
    if (value == null)
      return(false);
    final List<String> items = value.trim().split(new RegExp(r"\s+"));
    for (String item in items) {
      if (_simpleType != null) {
        if (!_simpleType.validValue(item))
          return(false);
      } else {
        if (!WXSSimpleType.validateTypeValue(DaxeWXS._localValue(_itemType), item))
          return(false);
      }
    }
    return(true);
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_redefine.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_redefine.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;



class WXSRedefine implements WXSThing, Parent {
  
  // annotations : inutile ici
  List<WXSThing> _redefinables; // (simpleType|complexType|group|attributeGroup)
  String _schemaLocation = null; // URI
  
  WXSSchema _schemaInclu = null;
  WXSSchema _schema;
  
  
  WXSRedefine(final Element el, final WXSSchema schema) {
    _redefinables = new List<WXSThing>();
    for (Node n = el.firstChild; n != null; n=n.nextSibling) {
      if (n is Element) {
        if (n.localName == "simpleType")
          _redefinables.add(new WXSSimpleType(n as Element, this, schema));
        else if (n.localName == "complexType")
          _redefinables.add(new WXSComplexType(n as Element, this, schema));
        else if (n.localName == "group")
          _redefinables.add(new WXSGroup(n as Element, this, schema));
        else if (n.localName == "attributeGroup")
          _redefinables.add(new WXSAttributeGroup(n as Element, this, schema));
      }
    }
    if (el.hasAttribute("schemaLocation"))
      _schemaLocation = el.getAttribute("schemaLocation");
    
    this._schema = schema;
  }
  
  Future _inclusions(final WXSSchema schema) { // can throw a WXSException
    Completer completer = new Completer();
    schema.newIncludedSchema(_schemaLocation, null, schema).then((WXSSchema schema) {
      _schemaInclu = schema;
      completer.complete();
    }, onError: (WXSException ex) {
      completer.completeError(ex);
    });
    return(completer.future);
  }
  
  List<WXSThing> getRedefinables() {
    return(_redefinables);
  }
  
  // from Parent
  List<WXSElement> parentElements() {
    return(new List<WXSElement>());
  }
  
  String getNamespace() {
    return(_schema.getTargetNamespace());
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_restriction.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_restriction.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;


class WXSRestriction extends WXSAnnotated implements WithSubElements, Parent {
  
  // simpleType?, (minExclusive|minInclusive|maxExclusive|maxInclusive|totalDigits|fractionDigits|length|minLength|maxLength|enumeration|pattern)*
  // ou: (group|all|choice|sequence)?, (attribute|attributeGroup)*
  WXSSimpleType _simpleType = null;
  List<WXSFacet> _facets;
  WithSubElements _model = null; // WXSGroup | WXSAll | WXSChoice | WXSSequence
  List<WXSThing> _attrDecls; // attrDecls: (attribute|attributeGroup)*
  String _base = null;
  
  WXSType _wxsBase = null;
  
  Element _domElement;
  WXSComplexContent _parent;
  
  
  WXSRestriction(final Element el, final WXSComplexContent parent, final WXSSchema schema) {
    _parseAnnotation(el);
    _facets = new List<WXSFacet>();
    _attrDecls = new List<WXSThing>();
    for (Node n = el.firstChild; n != null; n=n.nextSibling) {
      if (n is Element) {
        String localName = n.localName;
        if (localName == "simpleType")
          _simpleType = new WXSSimpleType(n as Element, this, schema);
        else if (localName == "minExclusive")
          _facets.add(new WXSFacet(n as Element));
        else if (localName == "minInclusive")
          _facets.add(new WXSFacet(n as Element));
        else if (localName == "maxExclusive")
          _facets.add(new WXSFacet(n as Element));
        else if (localName == "maxInclusive")
          _facets.add(new WXSFacet(n as Element));
        else if (localName == "totalDigits")
          _facets.add(new WXSFacet(n as Element));
        else if (localName == "fractionDigits")
          _facets.add(new WXSFacet(n as Element));
        else if (localName == "length")
          _facets.add(new WXSFacet(n as Element));
        else if (localName == "minLength")
          _facets.add(new WXSFacet(n as Element));
        else if (localName == "maxLength")
          _facets.add(new WXSFacet(n as Element));
        else if (localName == "enumeration")
          _facets.add(new WXSFacet(n as Element));
        else if (localName == "pattern")
          _facets.add(new WXSFacet(n as Element));
        else if (localName == "group")
          _model = new WXSGroup(n as Element, this, schema);
        else if (localName == "all")
          _model = new WXSAll(n as Element, this, schema);
        else if (localName == "choice")
          _model = new WXSChoice(n as Element, this, schema);
        else if (localName == "sequence")
          _model = new WXSSequence(n as Element, this, schema);
        else if (localName == "attribute")
          _attrDecls.add(new WXSAttribute(n as Element, this, schema));
        else if (localName == "attributeGroup")
          _attrDecls.add(new WXSAttributeGroup(n as Element, this, schema));
      }
    }
    if (el.hasAttribute("base"))
      _base = el.getAttribute("base");
    
    _domElement = el;
    this._parent = parent;
  }
  
  // from WithSubElements
  void resolveReferences(final WXSSchema schema, final WXSThing redefine) {
    if (_simpleType != null)
      _simpleType.resolveReferences(schema, redefine);
    if (_model != null)
      _model.resolveReferences(schema, redefine);
    for (WXSThing attrDecl in _attrDecls) {
      if (attrDecl is WXSAttribute)
        attrDecl.resolveReferences(schema);
      else if (attrDecl is WXSAttributeGroup)
        attrDecl.resolveReferences(schema, redefine);
    }
    if (_base != null) {
      final String tns = _domElement.lookupNamespaceURI(DaxeWXS._namePrefix(_base));
      _wxsBase = schema.resolveTypeReference(DaxeWXS._localValue(_base), tns, redefine);
    }
  }
  
  // from WithSubElements
  List<WXSElement> allElements() {
    final List<WXSElement> liste = new List<WXSElement>();
    if (_model != null)
      liste.addAll(_model.allElements());
    return(liste);
  }
  
  // from WithSubElements
  List<WXSElement> subElements() {
    final List<WXSElement> liste = new List<WXSElement>();
    if (_model != null)
      liste.addAll(_model.subElements());
    return(liste);
  }
  
  // from Parent
  List<WXSElement> parentElements() {
    if (_parent is WXSComplexContent)
      return(_parent.parentElements());
    else
      return(new List<WXSElement>());
  }
  
  // from WithSubElements
  String regularExpression() {
    if (_model != null)
      return(_model.regularExpression());
    return(null);
  }
  
  // from WithSubElements
  bool requiredChild(final WXSElement child) {
    // renvoie null si l'enfant n'en est pas un
    if (_model != null)
      return(_model.requiredChild(child));
    return(null);
  }
  
  // from WithSubElements
  bool multipleChildren(final WXSElement child) {
    // renvoie null si l'enfant n'en est pas un
    if (_model != null)
      return(_model.multipleChildren(child));
    return(null);
  }
  
  List<String> possibleValues() {
    List<String> liste = null;
    for (WXSFacet facet in _facets) {
      if (facet.getFacet() == "enumeration") {
        if (liste == null)
          liste = new List<String>();
        liste.add(facet.getValue());
      }
    }
    return(liste);
  }
  
  List<String> suggestedValues() {
    return(possibleValues());
  }
  
  List<WXSAttribute> attributes() {
    final List<WXSAttribute> liste = new List<WXSAttribute>();
    for (WXSThing attrDecl in _attrDecls) {
      if (attrDecl is WXSAttribute)
        liste.add(attrDecl);
      else if (attrDecl is WXSAttributeGroup)
        liste.addAll(attrDecl.attributes());
    }
    if (_wxsBase is WXSComplexType) {
      final List<WXSAttribute> listeBase = (_wxsBase as WXSComplexType).attributes();
      final List<WXSAttribute> aRetirer = new List<WXSAttribute>();
      for (WXSAttribute attributRest in liste) {
        final String nomExt = attributRest.getName();
        final bool prohibited = attributRest.getUse() == "prohibited";
        for (WXSAttribute attributBase in listeBase)
          if (nomExt == attributBase.getName()) {
            if (prohibited)
              aRetirer.add(attributBase);
            else
              listeBase[listeBase.indexOf(attributBase)] = attributRest;
            break;
          }
      }
      for (WXSAttribute attribut in aRetirer)
        listeBase.remove(attribut);
          return(listeBase);
    }
    return(liste);
  }
  
  // from WithSubElements
  int validate(final List<WXSElement> subElements, final int start, final bool insertion) {
    if (_model == null)
      return(start);
    return(_model.validate(subElements, start, insertion));
  }
  
  // from WithSubElements
  bool isOptionnal() {
    if (_model != null)
      return(_model.isOptionnal());
    return(true);
  }
  
  bool validValue(final String value) {
    if (_wxsBase != null) {
      if (!_wxsBase.validValue(value))
        return(false);
    }
    bool enumerationOrPattern = false;
    for (final WXSFacet facet in _facets) {
      if (facet.getFacet() == "enumeration") {
        if (facet.validValue(value))
          return(true);
        enumerationOrPattern = true;
      } else if (facet.getFacet() == "pattern") {
        if (facet.validValue(value))
          return(true);
        enumerationOrPattern = true;
      } else if (!facet.validValue(value))
        return(false);
    }
    if (enumerationOrPattern)
      return(false);
    return(true);
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_schema.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_schema.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;



class WXSSchema implements WXSThing {
  
  List<WXSInclude> _includes;
  List<WXSImport> _imports;
  List<WXSRedefine> _redefines;
  LinkedHashMap<String, WXSSimpleType> _simpleTypes;
  LinkedHashMap<String, WXSComplexType> _complexTypes;
  LinkedHashMap<String, WXSGroup> _groups;
  LinkedHashMap<String, WXSAttributeGroup> _attributeGroups;
  LinkedHashMap<String, WXSElement> _elements;
  LinkedHashMap<String, WXSAttribute> _attributes;
  String _targetNamespace = null;
  String _attributeFormDefault = null;
  String _elementFormDefault = null;
  
  String _url;
  DaxeWXS _jwxs;
  List<WXSSchema> _includedSchemas;
  WXSSchema _parentSchema;
  HashMap<String, String> _namespaceToPrefix;
  
  
  WXSSchema(final Element el, final String url, final DaxeWXS jwxs, final WXSSchema schemaParent) {
    this._url = url;
    this._jwxs = jwxs;
    this._parentSchema = schemaParent;
    _includes = new List<WXSInclude>();
    _imports = new List<WXSImport>();
    _redefines = new List<WXSRedefine>();
    _simpleTypes = new LinkedHashMap<String, WXSSimpleType>();
    _complexTypes = new LinkedHashMap<String, WXSComplexType>();
    _groups = new LinkedHashMap<String, WXSGroup>();
    _attributeGroups = new LinkedHashMap<String, WXSAttributeGroup>();
    _elements = new LinkedHashMap<String, WXSElement>();
    _attributes = new LinkedHashMap<String, WXSAttribute>();
    _includedSchemas = new List<WXSSchema>();
    for (Node n = el.firstChild; n != null; n=n.nextSibling) {
      if (n is Element) {
        String localName = n.localName;
        if (localName == "include")
          _includes.add(new WXSInclude(n as Element, this));
        else if (localName == "import")
          _imports.add(new WXSImport(n as Element, this));
        else if (localName == "redefine") {
          final WXSRedefine redefine = new WXSRedefine(n as Element, this);
          _redefines.add(redefine);
          for (WXSThing redefinable in redefine.getRedefinables()) {
            if (redefinable is WXSSimpleType) {
              final WXSSimpleType simpleType = redefinable;
              _simpleTypes[simpleType.getName()] = simpleType;
            } else if (redefinable is WXSComplexType) {
              final WXSComplexType complexType = redefinable;
              _complexTypes[complexType.getName()] = complexType;
            } else if (redefinable is WXSGroup) {
              final WXSGroup group = redefinable;
              _groups[group.getName()] = group;
            } else if (redefinable is WXSAttributeGroup) {
              final WXSAttributeGroup attributeGroup = redefinable;
              _attributeGroups[attributeGroup.getName()] = attributeGroup;
            }
          }
        } else if (localName == "simpleType") {
          final WXSSimpleType simpleType = new WXSSimpleType(n as Element, null, this);
          _simpleTypes[simpleType.getName()] = simpleType;
        } else if (localName == "complexType") {
          final WXSComplexType complexType = new WXSComplexType(n as Element, null, this);
          _complexTypes[complexType.getName()] = complexType;
        } else if (localName == "group") {
          final WXSGroup group = new WXSGroup(n as Element, null, this);
          _groups[group.getName()] = group;
        } else if (localName == "attributeGroup") {
          final WXSAttributeGroup attributeGroup = new WXSAttributeGroup(n as Element, null, this);
          _attributeGroups[attributeGroup.getName()] = attributeGroup;
        } else if (localName == "element") {
          final WXSElement element = new WXSElement(n as Element, null, this);
          _elements[element.getName()] = element;
        } else if (localName == "attribute") {
          final WXSAttribute attribute = new WXSAttribute(n as Element, null, this);
          _attributes[attribute.getName()] = attribute;
        }
      }
    }
    if (el.hasAttribute("targetNamespace")) {
      _targetNamespace = el.getAttribute("targetNamespace");
      if (_targetNamespace == "")
        _targetNamespace = null;
    }
    if (el.hasAttribute("attributeFormDefault"))
      _attributeFormDefault = el.getAttribute("attributeFormDefault");
    if (el.hasAttribute("elementFormDefault"))
      _elementFormDefault = el.getAttribute("elementFormDefault");
    
    _namespaceToPrefix = new HashMap<String, String>();
    if (el.attributes != null) {
      for (Attr att in el.attributes.values) {
        if (att.name.startsWith("xmlns:")) {
          final String prefixe = att.name.substring(6);
          _namespaceToPrefix[att.value] = prefixe;
        }
      }
    }
  }
  
  Future _inclusions() { // can throw a WXSException
    List<Future> futures = new List<Future>();
    for (WXSInclude include in _includes)
      futures.add(include._inclusions(this));
    for (WXSImport imp in _imports)
      futures.add(imp._inclusions(this));
    for (WXSRedefine redefine in _redefines)
      futures.add(redefine._inclusions(this));
    return(Future.wait(futures));
  }
  
  Iterable<WXSElement> getTopElements() {
    return(_elements.values);
  }
  
  Iterable<WXSAttribute> getTopAttributes() {
    return(_attributes.values);
  }
  
  String getTargetNamespace() {
    return(_targetNamespace);
  }
  
  String getAttributeFormDefault() {
    return(_attributeFormDefault);
  }
  
  String getElementFormDefault() {
    return(_elementFormDefault);
  }
  
  String getURL() {
    return(_url);
  }
  
  Future<WXSSchema> newIncludedSchema(final String schemaLocation,
      final String importNamespace, final WXSSchema parentSchema) { // can throw a WXSException
    Completer completer = new Completer();
    _jwxs._newIncludedSchema(_url, schemaLocation, importNamespace, parentSchema).then((WXSSchema schemaInclu) {
      if (schemaInclu != null && !_includedSchemas.contains(schemaInclu))
        _includedSchemas.add(schemaInclu);
      completer.complete(schemaInclu);
    }, onError: (WXSException ex) {
      completer.completeError(ex);
    });
    return(completer.future);
  }
  
  void _resolveReferences() {
    for (WXSSimpleType simpleType in _simpleTypes.values)
      simpleType.resolveReferences(this, simpleType.getParent() is WXSRedefine ? simpleType : null);
    for (WXSComplexType complexType in _complexTypes.values)
      complexType.resolveReferences(this, complexType.getParent() is WXSRedefine ? complexType : null);
    for (WXSGroup group in _groups.values)
      group.resolveReferences(this, group.getParent() is WXSRedefine ? group : null);
    for (WXSAttributeGroup attributeGroup in _attributeGroups.values)
      attributeGroup.resolveReferences(this, attributeGroup.getParent() is WXSRedefine ? attributeGroup : null);
    for (WXSElement element in _elements.values)
      element.resolveReferences(this, null);
    for (WXSAttribute attribute in _attributes.values)
      attribute.resolveReferences(this);
  }
  
  String namespacePrefix(final String ns) {
    return(_namespaceToPrefix[ns]);
  }
  
  String prefixNamespace(final String prefix) {
    if (prefix == null)
      return(null);
    for (String namespace in _namespaceToPrefix.keys) {
      if (prefix == _namespaceToPrefix[namespace])
        return(namespace);
    }
    return(null);
  }
  
  List<WXSElement> allElements() {
    final List<WXSElement> liste = new List<WXSElement>();
    for (WXSComplexType complexType in _complexTypes.values)
      liste.addAll(complexType.allElements());
    for (WXSGroup group in _groups.values)
      liste.addAll(group.allElements());
    for (WXSElement element in _elements.values)
      liste.addAll(element.allElements());
    return(liste);
  }
  
  List<WXSElement> anies(final String namespace) {
    return(_jwxs._anies(namespace, _targetNamespace));
  }
  
  WXSElement resolveElementReference(final String localName, final String namespace) {
    return(_resolveReference(localName, namespace, null, null, 'WXSElement') as WXSElement);
  }
  
  WXSType resolveTypeReference(final String localName, final String namespace, final WXSThing redefine) {
    return(_resolveReference(localName, namespace, null, redefine, 'WXSType') as WXSType);
  }
  
  WXSGroup resolveGroupReference(final String localName, final String namespace, final WXSThing redefine) {
    return(_resolveReference(localName, namespace, null, redefine, 'WXSGroup') as WXSGroup);
  }
  
  WXSAttributeGroup resolveAttributeGroupReference(final String localName, final String namespace, final WXSThing redefine) {
    return(_resolveReference(localName, namespace, null, redefine, 'WXSAttributeGroup') as WXSAttributeGroup);
  }
  
  WXSAttribute resolveAttributeReference(final String localName, final String namespace) {
    return(_resolveReference(localName, namespace, null, null, 'WXSAttribute') as WXSAttribute);
  }
  
  WXSThing _resolveReference(final String localName, final String namespace, final HashSet<WXSSchema> exclude,
                             final WXSThing redefine, final String classe) {
    if (localName == null)
      return(null);
    HashSet<WXSSchema> exclude2 = null;
    if (_parentSchema != null && (exclude == null || !exclude.contains(_parentSchema))) {
      if (exclude2 == null) {
        if (exclude == null)
          exclude2 = new HashSet<WXSSchema>();
        else
          exclude2 = exclude;
        exclude2.add(this);
      }
      final WXSThing thing = _parentSchema._resolveReference(localName, namespace, exclude2, redefine, classe);
      if (thing != null)
        return(thing);
    }
    if ((namespace == null && _targetNamespace == null) || (namespace != null && namespace == _targetNamespace)) {
      WXSThing thing;
      if (classe == 'WXSElement')
        thing = _elements[localName];
      else if (classe == 'WXSType') {
        thing = _complexTypes[localName];
        if (thing != null && thing != redefine)
          return(thing);
        thing = _simpleTypes[localName];
      } else if (classe == 'WXSGroup')
        thing = _groups[localName];
      else if (classe == 'WXSAttributeGroup')
        thing = _attributeGroups[localName];
      else if (classe == 'WXSAttribute')
        thing = _attributes[localName];
      else
        thing = null;
      if (thing != null && thing != redefine)
        return(thing);
    }
    for (WXSSchema schemaInclu in _includedSchemas) {
      if (exclude == null || !exclude.contains(schemaInclu)) {
        if (exclude2 == null) {
          if (exclude == null)
            exclude2 = new HashSet<WXSSchema>();
          else
            exclude2 = exclude;
          exclude2.add(this);
        }
        final WXSThing thing = schemaInclu._resolveReference(localName, namespace, exclude2, redefine, classe);
        if (thing  != null)
          return(thing);
      }
    }
    return(null);
  }
  
  String elementTitle(final WXSElement el) {
    return(_jwxs._elementTitle(el));
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_selector.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_selector.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;


class WXSSelector extends WXSAnnotated {
  
  String _xpath = null;
  
  
  WXSSelector(final Element el) {
    if (el.hasAttribute("xpath"))
      _xpath = el.getAttribute("xpath");
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_sequence.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_sequence.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;



class WXSSequence extends WXSExplicitGroup {
  
  WXSSequence(final Element el, final Parent parent, final WXSSchema schema) {
    _parse(el, parent, schema);
  }
  
  
  // from WithSubElements
  int validate(final List<WXSElement> subElements, final int start, final bool insertion) {
    int nb = 0;
    for (int i=start; i<subElements.length; ) {
      if (nb >= _maxOccurs)
        return(i);
      int pos = i;
      for (WithSubElements nestedParticle in _nestedParticles) {
        int pos2 = nestedParticle.validate(subElements, pos, insertion);
        if (pos2 == pos) {
          if (!insertion && !nestedParticle.isOptionnal()) {
            if (nb < _minOccurs)
              return(start);
            return(i);
          }
        }
        pos = pos2;
      }
      if (pos == i)
        return(i);
      i = pos;
      nb++;
    }
    if (!insertion && nb < _minOccurs)
      return(start);
    return(subElements.length);
  }
  
  // from WithSubElements
  bool isOptionnal() {
    if (_minOccurs == 0)
      return(true);
    for (WithSubElements nestedParticle in _nestedParticles) {
      if (!nestedParticle.isOptionnal())
        return(false);
    }
    return(true);
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_simple_content.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_simple_content.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;



class WXSSimpleContent extends WXSAnnotated {
  
  // (restriction|extension)
  WXSRestriction _restriction = null;
  WXSExtension _extension = null;
  
  
  WXSSimpleContent(final Element el, final WXSSchema schema) {
    _parseAnnotation(el);
    for (Node n = el.firstChild; n != null; n=n.nextSibling) {
      if (n is Element) {
        if (n.localName == "restriction")
          _restriction = new WXSRestriction(n as Element, null, schema);
        else if (n.localName == "extension")
          _extension = new WXSExtension(n as Element, null, schema);
      }
    }
  }
  
  void resolveReferences(final WXSSchema schema, final WXSThing redefine) {
    if (_restriction != null)
      _restriction.resolveReferences(schema, redefine);
    else if (_extension != null)
      _extension.resolveReferences(schema, redefine);
  }
  
  List<String> possibleValues() {
    if (_restriction != null)
      return(_restriction.possibleValues());
    else if (_extension != null)
      return(_extension.possibleValues());
    return(null);
  }
  
  List<String> suggestedValues() {
    if (_restriction != null)
      return(_restriction.suggestedValues());
    else if (_extension != null)
      return(_extension.suggestedValues());
    return(null);
  }
  
  List<WXSAttribute> attributes() {
    if (_restriction != null)
      return(_restriction.attributes());
    else if (_extension != null)
      return(_extension.attributes());
    return(new List<WXSAttribute>());
  }
  
  bool validValue(final String value) {
    if (_restriction != null)
      return(_restriction.validValue(value));
    if (_extension != null)
      return(_extension.validValue(value));
    return(false);
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_simple_type.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_simple_type.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;


class WXSSimpleType extends WXSAnnotated implements WXSType {
  
  // (restriction|list|union)
  WXSRestriction _restriction = null;
  WXSList _list = null;
  WXSUnion _union = null;
  String _name = null;
  
  Parent _parent; // WXSRedefine | WXSElement | WXSRestriction
  WXSSchema _schema;
  
  
  WXSSimpleType(final Element el, final Parent parent, final WXSSchema schema) {
    _parseAnnotation(el);
    for (Node n = el.firstChild; n != null; n=n.nextSibling) {
      if (n is Element) {
        if (n.localName == "restriction")
          _restriction = new WXSRestriction(n as Element, null, schema);
        else if (n.localName == "list")
          _list = new WXSList(n as Element, schema);
        else if (n.localName == "union")
          _union = new WXSUnion(n as Element, schema);
      }
    }
    if (el.hasAttribute("name"))
      _name = el.getAttribute("name");
    
    this._parent = parent;
    this._schema = schema;
  }
  
  // from WXSType
  String getName() {
    return(_name);
  }
  
  // from WXSType
  String getNamespace() {
    return(_schema.getTargetNamespace());
  }
  
  // from WXSType
  Parent getParent() {
    return(_parent);
  }
  
  // from WXSType
  void resolveReferences(final WXSSchema schema, final WXSThing redefine) {
    if (_restriction != null)
      _restriction.resolveReferences(schema, redefine);
    if (_list != null)
      _list.resolveReferences(schema, redefine);
    if (_union != null)
      _union.resolveReferences(schema, redefine);
  }
  
  // from WXSType
  List<String> possibleValues() {
    if (_restriction != null)
      return(_restriction.possibleValues());
    if (_union != null)
      return(_union.possibleValues());
    return(null);
  }
  
  // from WXSType
  List<String> suggestedValues() {
    if (_restriction != null)
      return(_restriction.suggestedValues());
    if (_union != null)
      return(_union.suggestedValues());
    return(null);
  }
  
  // from WXSType
  bool validValue(final String value) {
    if (_restriction != null)
      return(_restriction.validValue(value));
    if (_list != null)
      return(_list.validValue(value));
    if (_union != null)
      return(_union.validValue(value));
    return(false);
  }
  
  /**
   * Validation of a value by a simple type (the type must not have a prefix).
   */
  static bool validateTypeValue(final String type, final String value) {
    if (type == "string")
      return(true);
    else if (type == "normalizedString")
      return(_verifExpr(value, "[^\\t\\r\\n]*"));
    else if (type == "token") {
      if (value.indexOf('\n') != -1 || value.indexOf('\r') != -1 ||
          value.indexOf('\t') != -1 || value.indexOf("  ") != -1)
        return(false);
      return(!value.startsWith(" ") && !value.endsWith(" "));
    } else if (type == "base64Binary")
      return(_verifExpr(value, "(([a-zA-Z0-9+/=]\\s?){4})*"));
    else if (type == "hexBinary")
      return(_verifExpr(value, "(([0-9a-fA-F]){2})*"));
    else if (type == "integer")
      return(_verifExpr(value, "[+\\-]?\\d+"));
    else if (type == "positiveInteger")
      return(_verifExpr(value, "\\+?0*[1-9]\\d*"));
    else if (type == "negativeInteger")
      return(_verifExpr(value, "-0*[1-9]\\d*"));
    else if (type == "nonNegativeInteger")
      return(_verifExpr(value, "(-0+)|(\\+?\\d+)"));
    else if (type == "nonPositiveInteger")
      return(_verifExpr(value, "(\\+?0+)|(-\\d+)"));
    else if (type == "long") {
      if (!_verifExpr(value, "[+\\-]?\\d+"))
        return(false);
      try {
        final int big = int.parse(value);
        final int max = int.parse("9223372036854775807");
        final int min = int.parse("-9223372036854775808");
        if (big.compareTo(max) > 0)
          return(false);
        if (big.compareTo(min) < 0)
          return(false);
        return(true);
      } on FormatException catch(ex) {
        print("validerValeur(String, String) - FormatException $ex");
        return(false);
      }
    } else if (type == "unsignedLong") {
      if (!_verifExpr(value, "\\d+"))
        return(false);
      try {
        final int big = int.parse(value);
        final int max = int.parse("18446744073709551615");
        return(big.compareTo(max) <= 0);
      } on FormatException catch(ex) {
        print("validerValeur(String, String) - FormatException $ex");
        return(false);
      }
    } else if (type == "int") {
      if (!_verifExpr(value, "[+\\-]?\\d+"))
        return(false);
      String v2 = value;
      if (v2.startsWith("+"))
        v2 = v2.substring(1);
      try {
        final int val = int.parse(v2);
        return(val <= 2147483647 && val >= -2147483648);
      } on FormatException catch(ex) {
        print("validerValeur(String, String) - FormatException $ex");
        return(false);
      }
    } else if (type == "unsignedInt") {
      if (!_verifExpr(value, "\\d+"))
        return(false);
      try {
        final int val = int.parse(value);
        return(val <= 4294967295 && val >= 0);
      } on FormatException catch(ex) {
        print("validerValeur(String, String) - FormatException $ex");
        return(false);
      }
    } else if (type == "short") {
      if (!_verifExpr(value, "[+\\-]?\\d+"))
        return(false);
      String v2 = value;
      if (v2.startsWith("+"))
        v2 = v2.substring(1);
      try {
        final int val = int.parse(v2);
        return(val <= 32767 && val >= -32768);
      } on FormatException catch(ex) {
        print("validerValeur(String, String) - FormatException $ex");
        return(false);
      }
    } else if (type == "unsignedShort") {
      if (!_verifExpr(value, "\\d+"))
        return(false);
      try {
        final int val = int.parse(value);
        return(val <= 65535 && val >= 0);
      } on FormatException catch(ex) {
        print("validerValeur(String, String) - FormatException $ex");
        return(false);
      }
    } else if (type == "byte") {
      if (!_verifExpr(value, "[+\\-]?\\d+"))
        return(false);
      String v2 = value;
      if (v2.startsWith("+"))
        v2 = v2.substring(1);
      try {
        final int val = int.parse(v2);
        return(val <= 127 && val >= -128);
      } on FormatException catch(ex) {
        print("validerValeur(String, String) - FormatException $ex");
        return(false);
      }
    } else if (type == "unsignedByte") {
      if (!_verifExpr(value, "\\d+"))
        return(false);
      try {
        final int val = int.parse(value);
        return(val <= 255 && val >= 0);
      } on FormatException catch(ex) {
        print("validerValeur(String, String) - FormatException $ex");
        return(false);
      }
    } else if (type == "decimal") {
      return(_verifExpr(value, "[+\\-]?\\d+\\.?\\d*"));
    } else if (type == "float") {
      if (!_verifExpr(value, "(-?INF)|(NaN)|([+\\-]?\\d+\\.?\\d*([eE][+\\-]?\\d{1,3})?)"))
        return(false);
      if (value == "INF" || value == "-INF") // "Infinity" en Java
        return(true);
      try {
        double f = double.parse(value);
        if (log(f.abs())/LN2 > 127)
          return(false);
        if (log(f.abs())/LN2 < -126)
          return(false);
        return(true);
      } on FormatException {
        return(false);
      }
    } else if (type == "double") {
      if (!_verifExpr(value, "(-?INF)|(NaN)|([+\\-]?\\d+\\.?\\d*([eE][+\\-]?\\d{1,3})?)"))
        return(false);
      if (value == "INF" || value == "-INF")
        return(true);
      try {
        double.parse(value);
        return(true);
      } on FormatException {
        return(false);
      }
    } else if (type == "boolean")
      return(_verifExpr(value, "(true)|(false)|1|0"));
    else if (type == "duration")
      return(_verifExpr(value, "-?P(\\d{1,4}Y)?(\\d{1,2}M)?(\\d{1,2}D)?(T(\\d{1,2}H)?(\\d{1,2}M)?(\\d{1,2}(\\.\\d+)?S)?)?")); // en fait plus restrictif ("P" invalide par ex.)
    else if (type == "dateTime")
      return(_verifExpr(value, "-?\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d(\\.\\d+)?(([+\\-][01]\\d:\\d{2})|Z)?"));
    else if (type == "date")
      return(_verifExpr(value, "-?\\d{4}-[01]\\d-[0-3]\\d(([+\\-][01]\\d:\\d{2})|Z)?"));
    else if (type == "time")
      return(_verifExpr(value, "[0-2]\\d:[0-5]\\d:[0-5]\\d(\\.\\d+)?(([+\\-][01]\\d:\\d{2})|Z)?"));
    else if (type == "gYear")
      return(_verifExpr(value, "-?\\d{4}(([+\\-][01]\\d:\\d{2})|Z)?"));
    else if (type == "gYearMonth")
      return(_verifExpr(value, "-?\\d{4}-[01]\\d(([+\\-][01]\\d:\\d{2})|Z)?"));
    else if (type == "gMonth")
      return(_verifExpr(value, "--[01]\\d(([+\\-][01]\\d:\\d{2})|Z)?"));
    else if (type == "gMonthDay")
      return(_verifExpr(value, "--[01]\\d-[0-3]\\d(([+\\-][01]\\d:\\d{2})|Z)?"));
    else if (type == "gDay")
      return(_verifExpr(value, "---[0-3]\\d(([+\\-][01]\\d:\\d{2})|Z)?"));
    else if (type == "Name")
      return(_verifExpr(value, "[^<>&#!/?'\",0-9.\\-\\s][^<>&#!/?'\",\\s]*")); // en fait plus restrictif: \i\c*
    else if (type == "QName")
      return(_verifExpr(value, "[^<>&#!/?'\",0-9.\\-\\s][^<>&#!/?'\",\\s]*")); // en fait plus restrictif
    else if (type == "NCName")
      return(_verifExpr(value, "[^<>&#!/?'\",0-9.\\-\\s:][^<>&#!/?'\",:\\s]*")); // en fait plus restrictif: [\i-[:]][\c-[:]]*
    else if (type == "anyURI")
      return(true);
    //return(verifExpr(valeur, "([^:/?#]+:)?(//[^/?#]*)?[^?#]*(\\?[^#]*)?(#.*)?"));
    // pb: cette expression autorise tout!
    // (mais les RFC 2396 et 2732 ne restreignent rien)
    else if (type == "language")
      return(_verifExpr(value, "[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*"));
    else if (type == "ID")
      return(_verifExpr(value, "[^<>&#!/?'\",0-9.\\-\\s:][^<>&#!/?'\",:\\s]*")); // comme NCName
    else if (type == "IDREF")
      return(_verifExpr(value, "[^<>&#!/?'\",0-9.\\-\\s:][^<>&#!/?'\",:\\s]*")); // comme NCName
    else if (type == "IDREFS")
      return(_verifExpr(value, "[^<>&#!/?'\",0-9.\\-\\s:][^<>&#!/?'\",:]*"));
    else if (type == "ENTITY")
      return(_verifExpr(value, "[^<>&#!/?'\",0-9.\\-\\s:][^<>&#!/?'\",:\\s]*")); // comme NCName
    else if (type == "ENTITIES")
      return(_verifExpr(value, "[^<>&#!/?'\",0-9.\\-\\s:][^<>&#!/?'\",:]*")); // comme IDREFS
    else if (type == "NOTATION")
      return(_verifExpr(value, "[^0-9.\\-\\s][^\\s]*(\\s[^0-9.\\-\\s][^\\s]*)*"));
    // la facette enumeration est obligatoire -> contrainte supplémentaire
    else if (type == "NMTOKEN")
      return(_verifExpr(value, "[^<>&#!/?'\",\\s]+")); // en fait plus restrictif: \c+
    else if (type == "NMTOKENS")
      return(_verifExpr(value, "[^<>&#!/?'\",]+")); // en fait plus restrictif
    else
      return(true);
  }
  
  static bool _verifExpr(final String value, final String regexp) {
    // un cache serait-il utile ici ? (attention aux fuites de mémoire si c'est static)
    final RegExp r = new RegExp("^${regexp}\$");
    return(r.hasMatch(value));
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_thing.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_thing.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;



abstract class WXSThing {
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_type.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_type.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;


/**
 * Simple or complex type.
 */
abstract class WXSType {
  
  String getName();
  
  String getNamespace();
  
  Parent getParent();
  
  void resolveReferences(final WXSSchema schema, final WXSThing redefine);
  
  /**
   * Returns the list of possible values for this type if it is a simple type or a complex type with simple content.
   * Returns null if the list of possible values is infinite.
   */
  List<String> possibleValues();
  
  /**
   * Returns a list of values to suggest the user for this type if it is a simple type or a complex type with simple content.
   * Returns null if there is no interesting value to suggest.
   */
  List<String> suggestedValues();
  
  /**
   * Returns true if the value is valid for the simple type or the complex type with simple content.
   */
  bool validValue(final String value);
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_union.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_union.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;



class WXSUnion extends WXSAnnotated {
  
  // (simpleType)*
  List<WXSSimpleType> _simpleTypes;
  List<String> _memberTypes = null;
  
  Element _domElement;
  List<WXSSimpleType> _wxsMemberTypes;
  
  
  WXSUnion(final Element el, final WXSSchema schema) {
    _parseAnnotation(el);
    _simpleTypes = new List<WXSSimpleType>();
    for (Node n = el.firstChild; n != null; n=n.nextSibling) {
      if (n is Element && n.localName == "simpleType")
        _simpleTypes.add(new WXSSimpleType(n as Element, null, schema));
    }
    if (el.hasAttribute("memberTypes"))
      _memberTypes = (el.getAttribute("memberTypes")).split(new RegExp(r"\s+"));
    
    _domElement = el;
    _wxsMemberTypes = null;
  }
  
  void resolveReferences(final WXSSchema schema, final WXSThing redefine) {
    for (WXSSimpleType simpleType in _simpleTypes)
      simpleType.resolveReferences(schema, redefine);
    if (_memberTypes != null) {
      _wxsMemberTypes = new List<WXSSimpleType>(_memberTypes.length);
      for (int i=0; i<_memberTypes.length; i++) {
        final String type = _memberTypes[i];
        final String tns = _domElement.lookupNamespaceURI(DaxeWXS._namePrefix(type));
        final WXSType wxsType = schema.resolveTypeReference(DaxeWXS._localValue(type), tns, redefine);
        if (wxsType is WXSSimpleType)
          _wxsMemberTypes[i] = wxsType;
        else {
          _wxsMemberTypes[i] = null;
          final String espaceSchema = _domElement.namespaceURI;
          if (espaceSchema != tns)
            _memberTypes[i] = null; // si le type n'a pas été résolu il doit être un type des schémas XML
        }
      }
    }
  }
  
  List<String> possibleValues() {
    final List<String> liste = new List<String>();
    if (_memberTypes != null) {
      for (int i=0; i<_memberTypes.length; i++) {
        if (_wxsMemberTypes[i] != null) {
          final List<String> lv = _wxsMemberTypes[i].possibleValues();
          if (lv == null)
            return(null);
          liste.addAll(lv);
        } else {
          final String type = _memberTypes[i];
          final String tns = _domElement.lookupNamespaceURI(DaxeWXS._namePrefix(type));
          final String espaceSchema = _domElement.namespaceURI;
          if (espaceSchema == tns) {
            final List<String> lv = DaxeWXS._booleanValues(type, _domElement);
            if (lv == null)
              return(null);
            liste.addAll(lv);
          }
        }
      }
    }
    for (WXSSimpleType st in _simpleTypes) {
      final List<String> listest = st.possibleValues();
      if (listest == null)
        return(null);
      liste.addAll(listest);
    }
    if (liste.length == 0)
      return(null);
    return(liste);
  }
  
  List<String> suggestedValues() {
    final List<String> liste = new List<String>();
    if (_memberTypes != null) {
      for (int i=0; i<_memberTypes.length; i++) {
        if (_wxsMemberTypes[i] != null) {
          final List<String> lv = _wxsMemberTypes[i].possibleValues();
          if (lv != null)
            liste.addAll(lv);
        } else {
          final String type = _memberTypes[i];
          final String tns = _domElement.lookupNamespaceURI(DaxeWXS._namePrefix(type));
          final String espaceSchema = _domElement.namespaceURI;
          if (espaceSchema == tns) {
            final List<String> lv = DaxeWXS._booleanValues(type, _domElement);
            if (lv != null)
              liste.addAll(lv);
          }
        }
      }
    }
    for (WXSSimpleType st in _simpleTypes) {
      final List<String> listest = st.possibleValues();
      if (listest != null)
        liste.addAll(listest);
    }
    if (liste.length == 0)
      return(null);
    return(liste);
  }
  
  bool validValue(final String value) {
    if (_memberTypes != null) {
      for (int i=0; i<_memberTypes.length; i++) {
        if (_wxsMemberTypes[i] != null) {
          if (_wxsMemberTypes[i].validValue(value))
            return(true);
        } else if (_memberTypes[i] != null) {
          if (WXSSimpleType.validateTypeValue(DaxeWXS._localValue(_memberTypes[i]), value))
            return(true);
        }
      }
    }
    for (final WXSSimpleType st in _simpleTypes) {
      if (st.validValue(value))
        return(true);
    }
    return(false);
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_unique.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/wxs/wxs_unique.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of wxs;


class WXSUnique extends WXSKeybase {
  
  WXSUnique(final Element el) {
    _parse(el);
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/attr.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/attr.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

abstract class Attr extends Node {
  String name;
  String value;
  bool specified;
  Element ownerElement;
  bool isId;
  
  /*
  TypeInfo schemaTypeInfo;
   */
}

Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/attr_impl.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/attr_impl.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

/*
 * Entity references are not supported by this implementation.
 */
class AttrImpl extends NodeImpl implements Attr {
  bool specified;
  Element ownerElement;
  bool isId;
  
  AttrImpl.clone(final Attr attr) {
    specified = true;
    ownerElement = attr.ownerElement;
    isId = attr.isId;
    
    nodeName = attr.nodeName;
    nodeValue = attr.nodeValue;
    nodeType = attr.nodeType;
    parentNode = null;
    childNodes = null;
    firstChild = null;
    lastChild = null;
    previousSibling = attr.previousSibling;
    nextSibling = attr.nextSibling;
    attributes = null;
    ownerDocument = attr.ownerDocument;
    namespaceURI = attr.namespaceURI;
    prefix = attr.prefix;
    localName = attr.localName;
  }
  
  AttrImpl(final Document doc, final String name) {
    specified = true;
    ownerElement = null;
    isId = false;
    
    nodeName = name;
    nodeValue = null;
    nodeType = Node.ATTRIBUTE_NODE;
    parentNode = null;
    childNodes = null;
    firstChild = null;
    lastChild = null;
    previousSibling = null;
    nextSibling = null;
    attributes = null;
    ownerDocument = doc;
    namespaceURI = null;
    prefix = null;
    localName = null;
  }
  
  AttrImpl.NS(final Document doc, final String namespaceURI, final String qualifiedName) {
    specified = true;
    ownerElement = null;
    isId = false;
    
    nodeName = qualifiedName;
    nodeValue = null;
    nodeType = Node.ATTRIBUTE_NODE;
    parentNode = null;
    childNodes = null;
    firstChild = null;
    lastChild = null;
    previousSibling = null;
    nextSibling = null;
    attributes = null;
    ownerDocument = doc;
    this.namespaceURI = namespaceURI;
    int ind = name.indexOf(":");
    if (ind != -1) {
      prefix = name.substring(0, ind);
      localName = name.substring(ind+1);
    } else {
      prefix = null;
      localName = name;
    }
  }
  
  /*
  AttrImpl.fromDH(final Document doc, final String localName, final String value) {
    specified = true;
    ownerElement = null;
    isId = false;
    
    nodeName = localName; // KNOWN BUG: should be name
    nodeValue = value;
    nodeType = Node.ATTRIBUTE_NODE;
    parentNode = null;
    childNodes = null;
    firstChild = null;
    lastChild = null;
    previousSibling = null;
    nextSibling = null;
    attributes = null;
    ownerDocument = doc;
    namespaceURI = null;
    prefix = null; // KNOW BUG, see http://code.google.com/p/dart/issues/detail?id=8521
    this.localName = localName;
  }
  */
  
  Node cloneNode(bool deep) {
    return(new AttrImpl.clone(this));
  }
  
  String get name {
    return(nodeName);
  }
  
  void set name(String name) {
    nodeName = name;
  }
  
  String get value {
    return(nodeValue);
  }
  
  void set value(String value) {
    nodeValue = value;
  }
  
  String toString() {
    return('$nodeName="${NodeImpl._escape(nodeValue)}"');
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/cdata_section.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/cdata_section.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

abstract class CDATASection extends Text {
  
}


Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/cdata_section_impl.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/cdata_section_impl.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

class CDATASectionImpl extends NodeImpl implements CDATASection {
  
  CDATASectionImpl.clone(final CDATASection cds) {
    nodeName = cds.nodeName;
    nodeValue = cds.nodeValue;
    nodeType = cds.nodeType;
    parentNode = null;
    childNodes = null;
    firstChild = null;
    lastChild = null;
    previousSibling = null;
    nextSibling = null;
    attributes = null;
    ownerDocument = cds.ownerDocument;
    namespaceURI = cds.namespaceURI;
    prefix = null;
    localName = null;
  }
  
  CDATASectionImpl(final Document doc, final String data) {
    nodeName = "#cdata-section";
    nodeValue = data;
    nodeType = Node.CDATA_SECTION_NODE;
    parentNode = null;
    childNodes = null;
    firstChild = null;
    lastChild = null;
    previousSibling = null;
    nextSibling = null;
    attributes = null;
    ownerDocument = doc;
    namespaceURI = null;
    prefix = null;
    localName = null;
  }
  
  /*
  CDATASectionImpl.fromDH(final Document doc, final h.CDataSection cds) {
    nodeName = "#cdata-section";
    nodeValue = cds.nodeValue;
    nodeType = Node.CDATA_SECTION_NODE;
    parentNode = null;
    childNodes = null;
    firstChild = null;
    lastChild = null;
    previousSibling = null;
    nextSibling = null;
    attributes = null;
    ownerDocument = doc;
    namespaceURI = null;
    prefix = null;
    localName = null;
  }
  */
  
  Node cloneNode(bool deep) {
    return(new CDATASectionImpl.clone(this));
  }
  
  String toString() {
    String value = nodeValue;
    if (value == null)
      value = '';
    return("<![CDATA[$value]]>");
  }
}


Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/comment.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/comment.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

abstract class Comment extends Node {
  
}


Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/comment_impl.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/comment_impl.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

class CommentImpl extends NodeImpl implements Comment {
  
  CommentImpl.clone(final Comment com) {
    nodeName = "#comment";
    nodeValue = com.nodeValue;
    nodeType = com.nodeType;
    parentNode = null;
    childNodes = null;
    firstChild = null;
    lastChild = null;
    previousSibling = null;
    nextSibling = null;
    attributes = null;
    ownerDocument = com.ownerDocument;
    namespaceURI = com.namespaceURI;
    prefix = null;
    localName = null;
  }
  
  CommentImpl(final Document doc, final String data) {
    nodeName = "#comment";
    nodeValue = data;
    nodeType = Node.COMMENT_NODE;
    parentNode = null;
    childNodes = null;
    firstChild = null;
    lastChild = null;
    previousSibling = null;
    nextSibling = null;
    attributes = null;
    ownerDocument = doc;
    namespaceURI = null;
    prefix = null;
    localName = null;
  }
  
  /*
  CommentImpl.fromDH(final Document doc, final h.Comment com) {
    nodeName = "#comment";
    nodeValue = com.nodeValue;
    nodeType = Node.COMMENT_NODE;
    parentNode = null;
    childNodes = null;
    firstChild = null;
    lastChild = null;
    previousSibling = null;
    nextSibling = null;
    attributes = null;
    ownerDocument = doc;
    namespaceURI = null;
    prefix = null;
    localName = null;
  }
  */
  
  Node cloneNode(bool deep) {
    return(new CommentImpl.clone(this));
  }
  
  String toString() {
    return("<!--$nodeValue-->");
  }
}


Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/document.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/document.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

abstract class Document extends Node {
  DOMImplementation implementation;
  Element documentElement;
  String inputEncoding;
  String xmlEncoding;
  bool xmlStandalone;
  String xmlVersion;
  String documentURI;
  DocumentType doctype;
  
  Element createElement(String tagName); // throws DOMException
  DocumentFragment createDocumentFragment();
  Text createTextNode(String data);
  Comment createComment(String data);
  CDATASection createCDATASection(String data); // throws DOMException
  ProcessingInstruction createProcessingInstruction(String target, String data); // throws DOMException
  Attr createAttribute(String name); // throws DOMException
  EntityReference createEntityReference(String name); // throws DOMException
  List<Node> getElementsByTagName(String tagname);
  Node importNode(Node importedNode, bool deep); // throws DOMException
  Element createElementNS(String namespaceURI, String qualifiedName); // throws DOMException
  Attr createAttributeNS(String namespaceURI, String qualifiedName); // throws DOMException
  List<Node> getElementsByTagNameNS(String namespaceURI, String localName);
  Node adoptNode(Node source); // throws DOMException
  
  // unsupported features
  /*
  Element getElementById(String elementId);
  bool strictErrorChecking;
  DOMConfiguration domConfig;
  void normalizeDocument();
  Node renameNode(Node n, String namespaceURI, String qualifiedName); // throws DOMException
  */
}


Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/document_fragment.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/document_fragment.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

abstract class DocumentFragment extends Node {
  
}


Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/document_fragment_impl.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/document_fragment_impl.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

class DocumentFragmentImpl extends NodeImpl implements DocumentFragment {
  
  DocumentFragmentImpl.clone(final DocumentFragment df, final bool deep) {
    nodeName = df.nodeName;
    nodeValue = df.nodeValue;
    nodeType = df.nodeType;
    parentNode = null;
    if (deep && df.childNodes != null) {
      childNodes = new List<Node>();
      for (Node n in df.childNodes) {
        childNodes.add(n.cloneNode(deep));
      }
      if (childNodes.length > 0) {
        firstChild = childNodes[0];
        lastChild = childNodes[childNodes.length - 1];
      } else {
        firstChild = null;
        lastChild = null;
      }
    } else {
      childNodes = null;
      firstChild = null;
      lastChild = null;
    }
    previousSibling = null;
    nextSibling = null;
    attributes = null;
    ownerDocument = df.ownerDocument;
    namespaceURI = df.namespaceURI;
    prefix = null;
    localName = null;
  }
  
  DocumentFragmentImpl(final Document doc) {
    nodeName = "#document-fragment";
    nodeValue = null;
    nodeType = Node.DOCUMENT_FRAGMENT_NODE;
    parentNode = null;
    childNodes = null;
    firstChild = null;
    lastChild = null;
    previousSibling = null;
    nextSibling = null;
    attributes = null;
    ownerDocument = doc;
    namespaceURI = null;
    prefix = null;
    localName = null;
  }
  
  /*
  DocumentFragmentImpl.fromDH(final Document doc, final h.DocumentFragment df) {
    nodeName = "#document-fragment";
    nodeValue = df.nodeValue;
    nodeType = Node.DOCUMENT_FRAGMENT_NODE;
    parentNode = null;
    childNodes = null;
    firstChild = null;
    lastChild = null;
    previousSibling = null;
    nextSibling = null;
    attributes = null;
    ownerDocument = doc;
    namespaceURI = null;
    prefix = null;
    localName = null;
  }
  */
  
  Node cloneNode(bool deep) {
    return(new DocumentFragmentImpl.clone(this, deep));
  }
  
  String toString() {
    StringBuffer sb = new StringBuffer();
    for (Node n in childNodes) {
      sb.write(n.toString());
    }
    return(sb.toString());
  }
}


Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/document_impl.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/document_impl.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

class DocumentImpl extends NodeImpl implements Document {
  DOMImplementation implementation;
  Element documentElement;
  String inputEncoding;
  String xmlEncoding;
  bool xmlStandalone;
  String xmlVersion;
  String documentURI;
  DocumentType doctype;
  
  HashMap<String, Element> _idToElement; // todo: fill this with something !
  
  
  DocumentImpl.clone(final Document doc, final bool deep) {
    implementation = doc.implementation;
    inputEncoding = doc.inputEncoding;
    xmlEncoding = doc.xmlEncoding;
    xmlStandalone = doc.xmlStandalone;
    xmlVersion = doc.xmlVersion;
    documentURI = doc.documentURI;
    doctype = doc.doctype;
    
    _idToElement = new HashMap<String, Element>();
    
    nodeName = doc.nodeName;
    nodeValue = doc.nodeValue;
    nodeType = doc.nodeType;
    parentNode = null;
    documentElement = null;
    if (deep && doc.childNodes != null) {
      childNodes = new List<Node>();
      for (Node n in doc.childNodes) {
        Node n2 = n.cloneNode(deep);
        childNodes.add(n2);
        if (n2 is Element)
          documentElement = n2;
      }
      if (childNodes.length > 0) {
        firstChild = childNodes[0];
        lastChild = childNodes[childNodes.length - 1];
      } else {
        firstChild = null;
        lastChild = null;
      }
    } else {
      childNodes = null;
      firstChild = null;
      lastChild = null;
    }
    previousSibling = doc.previousSibling;
    nextSibling = doc.nextSibling;
    attributes = null;
    ownerDocument = doc.ownerDocument;
    namespaceURI = doc.namespaceURI;
    prefix = doc.prefix;
    localName = doc.localName;
  }
  
  DocumentImpl(final DOMImplementation implementation, final String namespaceURI,
               final String qualifiedName, final DocumentType doctype) {
    this.implementation = implementation;
    inputEncoding = null;
    xmlEncoding = "UTF-8";
    xmlStandalone = false;
    xmlVersion = "1.0";
    documentURI = null;
    this.doctype = doctype;
    _idToElement = new HashMap<String, Element>();
    
    nodeName = "#document";
    nodeValue = null;
    nodeType = Node.DOCUMENT_NODE;
    parentNode = null;
    childNodes = null;
    firstChild = null;
    lastChild = null;
    previousSibling = null;
    nextSibling = null;
    attributes = null;
    ownerDocument = null;
    this.namespaceURI = null;
    prefix = null;
    localName = null;
    
    if (qualifiedName != null) {
      if (namespaceURI != null)
        documentElement = new ElementImpl.NS(this, namespaceURI, qualifiedName);
      else
        documentElement = new ElementImpl(this, qualifiedName);
      documentElement.parentNode = this;
    }
  }
  
  /*
  DocumentImpl.fromDH(final DOMImplementation implementation, final h.Document doc) {
    // lots of info missing !
    this.implementation = implementation;
    inputEncoding = null;
    xmlEncoding = doc.charset;
    xmlStandalone = false;
    xmlVersion = "1.0";
    documentURI = null;
    doctype = null; // new DocumentTypeImpl.fromDH(this, doc.doctype);
    
    _idToElement = new HashMap<String, Element>();
    
    nodeName = "#document";
    nodeValue = doc.nodeValue;
    nodeType = Node.DOCUMENT_NODE;
    parentNode = null;
    childNodes = null;
    firstChild = null;
    lastChild = null;
    previousSibling = null;
    nextSibling = null;
    attributes = null;
    ownerDocument = null;
    namespaceURI = null;
    prefix = null;
    localName = null;
    
    appendChild(new ElementImpl.fromDH(this, doc.documentElement));
  }
  */
  
  Element createElement(String tagName) { // throws DOMException
    return(new ElementImpl(this, tagName));
  }
  
  DocumentFragment createDocumentFragment() {
    return(new DocumentFragmentImpl(this));
  }
  
  Text createTextNode(String data) {
    return(new TextImpl(this, data));
  }
  
  Comment createComment(String data) {
    return(new CommentImpl(this, data));
  }
  
  CDATASection createCDATASection(String data) { // throws DOMException
    return(new CDATASectionImpl(this, data));
  }
  
  ProcessingInstruction createProcessingInstruction(String target, String data) { // throws DOMException
    return(new ProcessingInstructionImpl(this, target, data));
  }
  
  Attr createAttribute(String name) { // throws DOMException
    return(new AttrImpl(this, name));
  }
  
  EntityReference createEntityReference(String name) { // throws DOMException
    return(new EntityReferenceImpl(this, name));
  }
  
  List<Node> getElementsByTagName(String tagname) {
    List<Node> nodes = new List<Node>();
    for (Node child in childNodes) {
      if (tagname == '*' || child.nodeName == tagname)
        nodes.add(child);
      if (child is Element)
        nodes.addAll(child.getElementsByTagName(tagname));
    }
    return(nodes);
  }
  
  Node importNode(Node importedNode, bool deep) { // throws DOMException
    Node clone = importedNode.cloneNode(deep);
    if (clone.nodeType == Node.ENTITY_NODE || clone.nodeType == Node.NOTATION_NODE) {
      clone.ownerDocument = this;
    } else
      adoptNode(clone);
    return(clone);
  }
  
  Element createElementNS(String namespaceURI, String qualifiedName) { // throws DOMException
    return(new ElementImpl.NS(this, namespaceURI, qualifiedName));
  }
  
  Attr createAttributeNS(String namespaceURI, String qualifiedName) { // throws DOMException
    return(new AttrImpl.NS(this, namespaceURI, qualifiedName));
  }
  
  List<Node> getElementsByTagNameNS(String namespaceURI, String localName) {
    List<Node> nodes = new List<Node>();
    for (Node child in childNodes) {
      if (child.namespaceURI == namespaceURI && child.localName == localName)
        nodes.add(child);
      if (child is Element)
        nodes.addAll(child.getElementsByTagNameNS(namespaceURI, localName));
    }
    return(nodes);
  }
  
  /*
  Element getElementById(String elementId) {
    return(_idToElement[elementId]);
  }
  */
  
  Node adoptNode(Node source) { // throws DOMException
    if (source.nodeType == Node.DOCUMENT_NODE || source.nodeType == Node.DOCUMENT_TYPE_NODE ||
        source.nodeType == Node.ENTITY_NODE || source.nodeType == Node.NOTATION_NODE)
      throw new DOMException("NOT_SUPPORTED_ERR");
    
    if (source.parentNode != null) {
      source.parentNode.removeChild(source);
    }
    
    if (source is Attr) {
      Attr att = source;
      att.ownerElement = null;
      att.specified = true;
    }
    
    _adoptNodeRecursion(source);
    return(source);
  }
  
  void _adoptNodeRecursion(Node source) {
    source.ownerDocument = this;
    if (source.attributes != null) {
      List<String> removeKeys = new List<String>();
      for (Attr att in source.attributes.values) {
        if (att.specified)
          att.ownerDocument = this;
        else
          removeKeys.add(att.name);
      }
      for (String attname in removeKeys) {
        source.attributes.remove(attname);
      }
    }
    if (source.childNodes != null) {
      for (Node n in source.childNodes) {
        if (n.nodeType != Node.DOCUMENT_TYPE_NODE && n.nodeType != Node.ENTITY_NODE &&
            n.nodeType != Node.NOTATION_NODE)
        _adoptNodeRecursion(n);
      }
    }
  }
  
  Node cloneNode(bool deep) {
    return(new DocumentImpl.clone(this, deep));
  }
  
  String toString() {
    StringBuffer sb = new StringBuffer();
    sb.write("<?xml");
    if (xmlVersion != null)
      sb.write(' version="$xmlVersion"');
    if (xmlEncoding != null)
      sb.write(' encoding="$xmlEncoding"');
    sb.writeln("?>");
    if (doctype != null)
      sb.write(doctype.toString());
    for (Node n in childNodes) {
      sb.write(n.toString());
    }
    return(sb.toString());
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/document_type.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/document_type.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

abstract class DocumentType extends Node {
  String name;
  String publicId;
  String systemId;
  
  /*
  LinkedHashMap<String, Entity> entities;
  LinkedHashMap<String, Notation> notations;
  String internalSubset;
  */
}

Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/document_type_impl.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/document_type_impl.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

class DocumentTypeImpl extends NodeImpl implements DocumentType {
  String name;
  String publicId;
  String systemId;
  
  DocumentTypeImpl.clone(final DocumentType dt) {
    name = dt.name;
    publicId = dt.publicId;
    systemId = dt.systemId;
    
    nodeName = dt.nodeName;
    nodeValue = dt.nodeValue;
    nodeType = dt.nodeType;
    parentNode = null;
    childNodes = null;
    firstChild = null;
    lastChild = null;
    previousSibling = null;
    nextSibling = null;
    attributes = null;
    ownerDocument = dt.ownerDocument;
    namespaceURI = dt.namespaceURI;
    prefix = null;
    localName = null;
  }
  
  DocumentTypeImpl(final String qualifiedName, final String publicId, final String systemId) {
    name = qualifiedName;
    this.publicId = publicId;
    this.systemId = systemId;
    
    nodeName = qualifiedName;
    nodeValue = null;
    nodeType = Node.DOCUMENT_TYPE_NODE;
    parentNode = null;
    childNodes = null;
    firstChild = null;
    lastChild = null;
    previousSibling = null;
    nextSibling = null;
    attributes = null;
    ownerDocument = null;
    namespaceURI = null;
    prefix = null;
    localName = null;
  }
  
  /*
  DocumentTypeImpl.fromDH(final Document doc, final h.DocumentType dt) {
    // BUG: name, publicId, systemId ???
    name = dt.$dom_localName;
    publicId = null;
    systemId = null;
    
    nodeName = name;
    nodeValue = dt.nodeValue;
    nodeType = Node.DOCUMENT_TYPE_NODE;
    parentNode = null;
    childNodes = null;
    firstChild = null;
    lastChild = null;
    previousSibling = null;
    nextSibling = null;
    attributes = null;
    ownerDocument = doc;
    namespaceURI = null;
    prefix = null;
    localName = null;
  }
  */
  
  Node cloneNode(bool deep) {
    return(new DocumentTypeImpl.clone(this));
  }
  
  String toString() {
    if (publicId == null && systemId != null)
      return('<!DOCTYPE $name SYSTEM "$systemId">');
    if (publicId != null && systemId != null)
      return('<!DOCTYPE $name PUBLIC "$publicId" "$systemId">');
    if (publicId != null)
      return('<!DOCTYPE $name PUBLIC "$publicId">');
    return('<!DOCTYPE $name>');
  }
}



Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/dom_exception.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/dom_exception.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

class DOMException implements Exception {
  
  final String message;
  final int errorCode;
  
  const DOMException(this.message, {this.errorCode});
  
  String toString() {
    String msg = "DOMException";
    if (message != null)
      msg += ": $message";
    if (errorCode != null)
      msg += ": $errorCode";
    return(msg);
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/dom_implementation.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/dom_implementation.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

abstract class DOMImplementation {
  
  bool hasFeature(String feature, String version);
  DocumentType createDocumentType(String qualifiedName, String publicId, String systemId); // throws DOMException
  Document createDocument(String namespaceURI, String qualifiedName, DocumentType doctype); // throws DOMException
  
  /*
  DOMObject getFeature(String feature, String version);
  */
}


Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/dom_implementation_impl.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/dom_implementation_impl.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

class DOMImplementationImpl implements DOMImplementation {
  
  DOMImplementationImpl();
  
  bool hasFeature(String feature, String version) {
    return(false);
  }
  
  DocumentType createDocumentType(String qualifiedName, String publicId, String systemId) { // throws DOMException
    return(new DocumentTypeImpl(qualifiedName, publicId, systemId));
  }
  
  Document createDocument(String namespaceURI, String qualifiedName, DocumentType doctype) { // throws DOMException
    return(new DocumentImpl(this, namespaceURI, qualifiedName, doctype));
  }
  
  /*
  Document createDocumentFromDH(h.Document doc) {
    return(new DocumentImpl.fromDH(this, doc));
  }
  */
}

Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/dom_parser.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/dom_parser.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

class DOMParser {
  
  Future<Document> parseFromURL(String url, {/*bool useBrowserXMLParser:false,*/ bool disableCache:true}) { // throws DOMException
    Completer<Document> completer = new Completer<Document>();
    // DomParser and responseXml don't work with IE9
    // http://code.google.com/p/dart/issues/detail?id=7937
    // xml-dart does not support XML comments, and its support for namespaces is limited
    h.HttpRequest request = new h.HttpRequest();
    // overrideMimeType doesn't work in IE (even IE10)
    //if (request.supportsOverrideMimeType)
    //  request.overrideMimeType('text/xml');
    if (disableCache) {
      // request.setRequestHeader('Pragma', 'no-cache');
      // request.setRequestHeader('Cache-Control', 'no-cache'); // or: private, max-age=0
      // This doesn't work with IE9
      // see http://stackoverflow.com/q/5235464/438970
      // When the URL does not include a script and might use Apache directly,
      // we have to add a random parameter to the URL to avoid the IE cache.
      if (!url.contains('.php?')) {
        Random rnd = new Random();
        String param = "random_workaround=${rnd.nextDouble()}";
        if (url.contains('?'))
          url = "$url&$param";
        else
          url = "$url?$param";
      }
    }
    request.open('GET', url);
    request.onLoad.listen((h.ProgressEvent event) {
      if (request.status != 200) {
        completer.completeError(new DOMException("Error reading $url",
            errorCode: request.status));
        return;
      }
      Document xdoc;
      /*
      if (useBrowserXMLParser) {
        h.Document hdoc = request.responseXml;
        if (hdoc == null) {
          if (request.responseText != null)
            completer.completeError(new DOMException("Error parsing XML from $url"));
          else
            completer.completeError(new DOMException("Error reading $url"));
          return;
        }
        // https://bugzilla.mozilla.org/show_bug.cgi?id=45566
        // http://stackoverflow.com/q/11563554/438970
        // http://code.google.com/p/dart/issues/detail?id=7938
        //FIXME: $dom_namespaceUri and localName seem to be deprecated, use tagName instead ?
        if (hdoc.documentElement.$dom_namespaceUri == "http://www.mozilla.org/newlayout/xml/parsererror.xml" &&
            hdoc.documentElement.localName == "parsererror") {
          completer.completeError(new DOMException("Error parsing XML from $url"));
          return;
        }
        DOMImplementationImpl dom = new DOMImplementationImpl();
        xdoc = dom.createDocumentFromDH(hdoc);
      } else {
      */
        // warning: at this point responseText can have the wrong character encoding with IE,
        // unless Unicode is used
        if (request.responseText == null) {
          completer.completeError(new DOMException("Error reading $url"));
          return;
        }
        try {
          xdoc = parseFromString(request.responseText);
        } on DOMException catch(ex) {
          completer.completeError(new DOMException("Error reading $url: ${ex.message}"));
          return;
        }
      //}
      completer.complete(xdoc);
    });
    request.onError.listen((h.ProgressEvent event) {
      completer.completeError(new DOMException("Error reading $url"));
    });
    request.send();
    return(completer.future);
  }
  
  /*
  Document parseFromStringWithDartDOM(String s) { // throws DOMException
    h.DomParser parser = new h.DomParser();
    h.Document hdoc = parser.parseFromString(s, "text/xml");
    DOMImplementationImpl dom = new DOMImplementationImpl();
    Document xdoc = dom.createDocumentFromDH(hdoc);
    if (xdoc.getElementsByTagName('parsererror').length > 0)
      throw new DOMException("Error parsing XML from string");
    return(xdoc);
  }
  */
  
  Document parseFromString(String s) { // throws DOMException
    XMLParser parser = new XMLParser();
    try {
      return(parser.parseString(s));
    } on Exception catch(ex) {
      throw new DOMException(ex.toString());
    }
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/element.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/element.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

abstract class Element extends Node {
  String tagName;
  
  String getAttribute(String name);
  void setAttribute(String name, String value); // throws DOMException
  void removeAttribute(String name); // throws DOMException
  Attr getAttributeNode(String name);
  Attr setAttributeNode(Attr newAttr); // throws DOMException
  Attr removeAttributeNode(Attr oldAttr); // throws DOMException
  List<Node> getElementsByTagName(String name);
  String getAttributeNS(String namespaceURI, String localName); // throws DOMException
  void setAttributeNS(String namespaceURI, String qualifiedName, String value); // throws DOMException
  void removeAttributeNS(String namespaceURI, String localName); // throws DOMException
  Attr getAttributeNodeNS(String namespaceURI, String localName); // throws DOMException
  Attr setAttributeNodeNS(Attr newAttr); // throws DOMException
  List<Node> getElementsByTagNameNS(String namespaceURI, String localName); // throws DOMException
  bool hasAttribute(String name);
  bool hasAttributeNS(String namespaceURI, String localName); // throws DOMException
  
  /*
  TypeInfo schemaTypeInfo;
  void setIdAttribute(String name, bool isId); // throws DOMException
  void setIdAttributeNS(String namespaceURI, String localName, bool isId); // throws DOMException
  void setIdAttributeNode(Attr idAttr, bool isId); // throws DOMException
  */
}


Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/element_impl.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/element_impl.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

class ElementImpl extends NodeImpl implements Element {
  String tagName;
  
  ElementImpl.clone(final Element el, final bool deep) {
    tagName = el.tagName;
    
    nodeName = el.nodeName;
    nodeValue = el.nodeValue;
    nodeType = el.nodeType;
    parentNode = null;
    if (deep && el.childNodes != null) {
      childNodes = new List<Node>();
      for (Node n in el.childNodes) {
        childNodes.add(n.cloneNode(deep));
      }
      if (childNodes.length > 0) {
        firstChild = childNodes[0];
        lastChild = childNodes[childNodes.length - 1];
      } else {
        firstChild = null;
        lastChild = null;
      }
    } else {
      childNodes = null;
      firstChild = null;
      lastChild = null;
    }
    previousSibling = null;
    nextSibling = null;
    if (el.attributes == null)
      attributes = null;
    else {
      attributes = new LinkedHashMap<String, Attr>();
      for (String name in el.attributes.keys) {
        attributes[name] = el.attributes[name].cloneNode(deep);
      }
    }
    ownerDocument = el.ownerDocument;
    namespaceURI = el.namespaceURI;
    prefix = el.prefix;
    localName = el.localName;
  }
  
  ElementImpl(final Document doc, final String tagName) {
    this.tagName = tagName;
    nodeName = tagName;
    nodeValue = null;
    nodeType = Node.ELEMENT_NODE;
    parentNode = null;
    childNodes = null;
    firstChild = null;
    lastChild = null;
    previousSibling = null;
    nextSibling = null;
    attributes = null;
    ownerDocument = doc;
    namespaceURI = null;
    prefix = null;
    localName = null;
  }
  
  ElementImpl.NS(final Document doc, final String namespaceURI, final String qualifiedName) {
    tagName = qualifiedName;
    nodeName = qualifiedName;
    nodeValue = null;
    nodeType = Node.ELEMENT_NODE;
    parentNode = null;
    childNodes = null;
    firstChild = null;
    lastChild = null;
    previousSibling = null;
    nextSibling = null;
    attributes = null;
    ownerDocument = doc;
    this.namespaceURI = namespaceURI;
    int ind = qualifiedName.indexOf(":");
    if (ind != -1) {
      prefix = qualifiedName.substring(0, ind);
      localName = qualifiedName.substring(ind+1);
    } else {
      prefix = null;
      localName = qualifiedName;
    }
  }
  
  /*
  ElementImpl.fromDH(final Document doc, final h.Element el) {
    tagName = el.tagName;
    nodeName = tagName;
    nodeValue = el.nodeValue;
    nodeType = Node.ELEMENT_NODE;
    parentNode = null;
    childNodes = null;
    firstChild = null;
    lastChild = null;
    previousSibling = null;
    nextSibling = null;
    if (el.attributes.length == 0) {
      attributes = null;
    } else {
      attributes = new LinkedHashMap<String, Attr>();
      /*
      h.NamedNodeMap nm = el.$dom_attributes;
      if (nm != null) {
        for (h.Node n in nm) {
          h.Attr att = n as h.Attr;
          setAttributeNode(new AttrImpl.fromDH(ownerDocument, att)); // BUG name != localName
        }
      }
      */
      // WE ARE MISSING ALL NAMESPACED ATTRIBUTES HERE !!!
      for (String attname in el.attributes.keys) {
        setAttribute(attname, el.attributes[attname]);
      }
    }
    ownerDocument = doc;
    namespaceURI = el.$dom_namespaceUri;
    int ind = tagName.indexOf(":");
    if (ind != -1) {
      prefix = tagName.substring(0, ind);
    } else {
      prefix = null;
    }
    localName = el.localName;
    
    for (h.Node n in el.nodes) {
      Node n2 = null;
      switch (n.nodeType) {
        case Node.ELEMENT_NODE:
          n2 = new ElementImpl.fromDH(ownerDocument, n as h.Element);
          break;
        case Node.ATTRIBUTE_NODE:
          //n2 = new AttrImpl.fromDH(ownerDocument, n as h.Attr);
          // no longer possible, attributes are now added elsewhere
          assert(false);
          break;
        case Node.TEXT_NODE:
          n2 = new TextImpl.fromDH(ownerDocument, n as h.Text);
          break;
        case Node.CDATA_SECTION_NODE:
          n2 = new CDATASectionImpl.fromDH(ownerDocument, n as h.CDataSection);
          break;
        case Node.ENTITY_REFERENCE_NODE:
          //n2 = new EntityReferenceImpl.fromDH(ownerDocument, n as h.EntityReference);
          n2 = null; // h.EntityReference has been removed from the API
          break;
        case Node.PROCESSING_INSTRUCTION_NODE:
          n2 = new ProcessingInstructionImpl.fromDH(ownerDocument, n as h.ProcessingInstruction);
          break;
        case Node.COMMENT_NODE:
          n2 = new CommentImpl.fromDH(ownerDocument, n as h.Comment);
          break;
      }
      if (n2 != null)
        appendChild(n2);
    }
    
    // merge text nodes (useful with Firefox)
    for (Node n = firstChild; n != null; n = n.nextSibling) {
      if (n.nodeType == Node.TEXT_NODE) {
        while (n.nextSibling != null && n.nextSibling.nodeType == Node.TEXT_NODE) {
          n.nodeValue += n.nextSibling.nodeValue;
          removeChild(n.nextSibling);
        }
      }
    }
  }
  */
  
  String getAttribute(String name) {
    if (attributes == null)
      return("");
    Attr att = attributes[name];
    if (att == null)
      return("");
    String v = att.nodeValue;
    if (v == null)
      return("");
    return(v);
  }
  
  void setAttribute(String name, String value) { // throws DOMException
    if (attributes == null)
      attributes = new LinkedHashMap<String, Attr>();
    Attr att = attributes[name];
    if (att == null) {
      att = new AttrImpl(ownerDocument, name);
      att.parentNode = this;
      att.ownerElement = this;
      attributes[name] = att;
    }
    att.nodeValue = value;
  }
  
  void removeAttribute(String name) { // throws DOMException
    attributes.remove(name);
  }
  
  Attr getAttributeNode(String name) {
    if (attributes == null)
      return(null);
    return(attributes[name]);
  }
  
  Attr setAttributeNode(Attr newAttr) { // throws DOMException
    if (ownerDocument != newAttr.ownerDocument)
      throw new DOMException("WRONG_DOCUMENT_ERR");
    
    if (attributes == null)
      attributes = new LinkedHashMap<String, Attr>();
    newAttr.parentNode = this;
    newAttr.ownerElement = this;
    attributes[newAttr.name] = newAttr;
    return(newAttr);
  }
  
  Attr removeAttributeNode(Attr oldAttr) { // throws DOMException
    if (attributes == null || !attributes.containsValue(oldAttr))
      throw new DOMException("NOT_FOUND_ERR");
    
    attributes.remove(oldAttr.name);
    return(oldAttr);
  }
  
  List<Node> getElementsByTagName(String name) {
    List<Node> nodes = new List<Node>();
    if (childNodes == null)
      return(nodes);
    for (Node child in childNodes) {
      if (name == '*' || child.nodeName == name)
        nodes.add(child);
      if (child is Element)
        nodes.addAll(child.getElementsByTagName(name));
    }
    return(nodes);
  }
  
  String getAttributeNS(String namespaceURI, String localName) { // throws DOMException
    if (attributes == null)
      return("");
    for (Attr att in attributes.values) {
      if (att.namespaceURI == namespaceURI && att.localName == localName) {
        if (att.value == null)
          return("");
        return(att.value);
      }
    }
    return("");
  }
  
  void setAttributeNS(String namespaceURI, String qualifiedName, String value) { // throws DOMException
    if (attributes == null)
      attributes = new LinkedHashMap<String, Attr>();
    String attPrefix, attLocalName;
    int ind = qualifiedName.indexOf(":");
    if (ind != -1) {
      attPrefix = qualifiedName.substring(0, ind);
      attLocalName = qualifiedName.substring(ind+1);
    } else {
      attPrefix = null;
      attLocalName = qualifiedName;
    }
    Attr att = getAttributeNodeNS(namespaceURI, attLocalName);
    if (att != null) {
      att.prefix = attPrefix;
      att.nodeValue = value;
      return;
    }
    att = new AttrImpl.NS(ownerDocument, namespaceURI, qualifiedName);
    att.parentNode = this;
    att.ownerElement = this;
    att.nodeValue = value;
    attributes[qualifiedName] = att;
  }
  
  void removeAttributeNS(String namespaceURI, String localName) { // throws DOMException
    Attr att = getAttributeNodeNS(namespaceURI, localName);
    if (att != null)
        attributes.remove(att.name);
  }
  
  Attr getAttributeNodeNS(String namespaceURI, String localName) { // throws DOMException
    if (attributes == null)
      return(null);
    for (Attr att in attributes.values) {
      if (att.namespaceURI == namespaceURI && att.localName == localName) {
        return(att);
      }
    }
    return(null);
  }
  
  Attr setAttributeNodeNS(Attr newAttr) { // throws DOMException
    if (ownerDocument != newAttr.ownerDocument)
      throw new DOMException("WRONG_DOCUMENT_ERR");
    if (newAttr.ownerElement != null)
      throw new DOMException("INUSE_ATTRIBUTE_ERR");
    
    if (attributes == null)
      attributes = new LinkedHashMap<String, Attr>();
    Attr att = getAttributeNodeNS(newAttr.namespaceURI, newAttr.localName);
    if (att != null)
      attributes.remove(att.name);
    attributes[newAttr.name] = newAttr;
    return(att);
  }
  
  List<Node> getElementsByTagNameNS(String namespaceURI, String localName) { // throws DOMException
    List<Node> nodes = new List<Node>();
    if (childNodes == null)
      return(nodes);
    for (Node child in childNodes) {
      if (child.namespaceURI == namespaceURI && child.localName == localName)
        nodes.add(child);
      if (child is Element)
        nodes.addAll(child.getElementsByTagNameNS(namespaceURI, localName));
    }
    return(nodes);
  }
  
  bool hasAttribute(String name) {
    if (attributes == null)
      return(false);
    return(attributes[name] != null);
  }
  
  bool hasAttributeNS(String namespaceURI, String localName) { // throws DOMException
    Attr att = getAttributeNodeNS(namespaceURI, localName);
    return(att != null);
  }
  
  Node cloneNode(bool deep) {
    return(new ElementImpl.clone(this, deep));
  }
  
  String toString() {
    StringBuffer sb = new StringBuffer();
    sb.write("<$tagName");
    if (attributes != null) {
      for (Attr att in attributes.values) {
        sb.write(" ");
        sb.write(att.toString());
      }
    }
    if (childNodes != null && childNodes.length > 0) {
      sb.write(">");
      for (Node n in childNodes) {
        sb.write(n.toString());
      }
      sb.write("</$tagName>");
    } else {
      sb.write("/>");
    }
    return(sb.toString());
  }
}




Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/entity_reference.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/entity_reference.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

abstract class EntityReference extends Node {
  
}


Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/entity_reference_impl.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/entity_reference_impl.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

class EntityReferenceImpl extends NodeImpl implements EntityReference {
  
  EntityReferenceImpl.clone(final EntityReference er) {
    nodeName = er.nodeName;
    nodeValue = er.nodeValue;
    nodeType = er.nodeType;
    parentNode = null;
    childNodes = null;
    firstChild = null;
    lastChild = null;
    previousSibling = null;
    nextSibling = null;
    attributes = null;
    ownerDocument = er.ownerDocument;
    namespaceURI = er.namespaceURI;
    prefix = null;
    localName = null;
  }
  
  EntityReferenceImpl(final Document doc, final String name) {
    nodeName = name;
    nodeValue = null;
    nodeType = Node.ENTITY_REFERENCE_NODE;
    parentNode = null;
    childNodes = null;
    firstChild = null;
    lastChild = null;
    previousSibling = null;
    nextSibling = null;
    attributes = null;
    ownerDocument = doc;
    namespaceURI = null;
    prefix = null;
    localName = null;
  }
  
  /* h.EntityReference has been removed from the API
  EntityReferenceImpl.fromDH(final Document doc, final h.EntityReference er) {
    // not implemented in Mozilla
    nodeName = er.localName; // BUG
    nodeValue = er.nodeValue;
    nodeType = Node.ENTITY_REFERENCE_NODE;
    parentNode = null;
    childNodes = null;
    firstChild = null;
    lastChild = null;
    previousSibling = null;
    nextSibling = null;
    attributes = null;
    ownerDocument = doc;
    namespaceURI = null;
    prefix = null;
    localName = null;
  }
  */
  
  Node cloneNode(bool deep) {
    return(new EntityReferenceImpl.clone(this));
  }
  
  String toString() {
    return("&$nodeName;");
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/node.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/node.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

abstract class Node {
  static const int ELEMENT_NODE                   = 1;
  static const int ATTRIBUTE_NODE                 = 2;
  static const int TEXT_NODE                      = 3;
  static const int CDATA_SECTION_NODE             = 4;
  static const int ENTITY_REFERENCE_NODE          = 5;
  static const int ENTITY_NODE                    = 6;
  static const int PROCESSING_INSTRUCTION_NODE    = 7;
  static const int COMMENT_NODE                   = 8;
  static const int DOCUMENT_NODE                  = 9;
  static const int DOCUMENT_TYPE_NODE             = 10;
  static const int DOCUMENT_FRAGMENT_NODE         = 11;
  static const int NOTATION_NODE                  = 12;

  String nodeName;
  String nodeValue;
  int  nodeType;
  Node parentNode;
  List<Node> childNodes;
  Node firstChild;
  Node lastChild;
  Node previousSibling;
  Node nextSibling;
  LinkedHashMap<String, Attr> attributes;
  Document ownerDocument;
  String namespaceURI;
  String prefix;
  String localName;
  
  Node insertBefore(Node newChild, Node refChild); // throws DOMException
  Node replaceChild(Node newChild, Node oldChild); // throws DOMException
  Node removeChild(Node oldChild); // throws DOMException
  Node appendChild(Node newChild); // throws DOMException
  bool hasChildNodes();
  Node cloneNode(bool deep);
  bool hasAttributes();
  String lookupPrefix(String namespaceURI);
  String lookupNamespaceURI(String prefix);

  // unsupported features
  
  //String baseURI;
  
  //String textContent;
  
  //void normalize();
  //bool isSupported(String feature, String version);
  // DocumentPosition
  //const int DOCUMENT_POSITION_DISCONNECTED = 0x01;
  //const int DOCUMENT_POSITION_PRECEDING    = 0x02;
  //const int DOCUMENT_POSITION_FOLLOWING    = 0x04;
  //const int DOCUMENT_POSITION_CONTAINS     = 0x08;
  //const int DOCUMENT_POSITION_CONTAINED_BY = 0x10;
  //const int DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 0x20;

  //int compareDocumentPosition(Node other); // throws DOMException

  //bool isSameNode(Node other);
  //bool isDefaultNamespace(String namespaceURI);
  //bool isEqualNode(Node arg);
  //DOMObject getFeature(String feature, String version);
  //DOMUserData setUserData(String key, DOMUserData data, UserDataHandler handler);
  //DOMUserData getUserData(String key);
}
Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/node_impl.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/node_impl.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

abstract class NodeImpl implements Node {
  
  static const int ELEMENT_NODE                   = 1;
  static const int ATTRIBUTE_NODE                 = 2;
  static const int TEXT_NODE                      = 3;
  static const int CDATA_SECTION_NODE             = 4;
  static const int ENTITY_REFERENCE_NODE          = 5;
  static const int ENTITY_NODE                    = 6;
  static const int PROCESSING_INSTRUCTION_NODE    = 7;
  static const int COMMENT_NODE                   = 8;
  static const int DOCUMENT_NODE                  = 9;
  static const int DOCUMENT_TYPE_NODE             = 10;
  static const int DOCUMENT_FRAGMENT_NODE         = 11;
  static const int NOTATION_NODE                  = 12;

  String nodeName;
  String nodeValue;
  int  nodeType;
  Node parentNode;
  List<Node> childNodes;
  Node firstChild;
  Node lastChild;
  Node previousSibling;
  Node nextSibling;
  LinkedHashMap<String, Attr> attributes;
  Document ownerDocument;
  String namespaceURI;
  String prefix;//TODO: prefix setter method
  String localName;
  
  Node insertBefore(Node newChild, Node refChild) { // throws DOMException
    if (childNodes == null || !childNodes.contains(refChild))
      throw new DOMException("NOT_FOUND_ERR");
    
    _checkNewChildValid(newChild);
    
    if (nodeType == DOCUMENT_NODE && newChild.nodeType == ELEMENT_NODE &&
        (this as Document).documentElement != null)
      throw new DOMException("HIERARCHY_REQUEST_ERR");
    
    if (childNodes == null)
      childNodes = new List<Node>();
    
    if (newChild.parentNode != null)
      newChild.parentNode.removeChild(newChild);
    
    newChild.parentNode = this;
    if (nodeType == DOCUMENT_NODE && newChild.nodeType == ELEMENT_NODE)
      (this as Document).documentElement = newChild;
    
    if (firstChild == refChild)
      firstChild = newChild;
    newChild.nextSibling = refChild;
    newChild.previousSibling = refChild.previousSibling;
    return(newChild);
  }
  
  Node replaceChild(Node newChild, Node oldChild) { // throws DOMException
    if (childNodes == null || !childNodes.contains(oldChild))
      throw new DOMException("NOT_FOUND_ERR");
    
    _checkNewChildValid(newChild);
    
    if (newChild.parentNode != null)
      newChild.parentNode.removeChild(newChild);
    
    newChild.parentNode = this;
    if (nodeType == DOCUMENT_NODE && newChild.nodeType == ELEMENT_NODE)
      (this as Document).documentElement = newChild;
    
    if (firstChild == oldChild)
      firstChild = newChild;
    if (lastChild == oldChild)
      lastChild = newChild;
    newChild.nextSibling = oldChild.nextSibling;
    newChild.previousSibling = oldChild.previousSibling;
    oldChild.parentNode = null;
    childNodes.remove(oldChild);
    return(oldChild);
  }
  
  Node removeChild(Node oldChild) { // throws DOMException
    if (childNodes == null || !childNodes.contains(oldChild))
      throw new DOMException("NOT_FOUND_ERR");
    
    if (nodeType == DOCUMENT_NODE && oldChild.nodeType == ELEMENT_NODE)
      (this as Document).documentElement = null;
    
    if (firstChild == oldChild)
      firstChild = oldChild.nextSibling;
    if (lastChild == oldChild)
      lastChild = oldChild.previousSibling;
    if (oldChild.previousSibling != null)
      oldChild.previousSibling.nextSibling = oldChild.nextSibling;
    if (oldChild.nextSibling != null)
      oldChild.nextSibling.previousSibling = oldChild.previousSibling;
    oldChild.parentNode = null;
    childNodes.remove(oldChild);
    return(oldChild);
  }
  
  Node appendChild(Node newChild) { // throws DOMException
    
    _checkNewChildValid(newChild);
    
    if (nodeType == DOCUMENT_NODE && newChild.nodeType == ELEMENT_NODE &&
        (this as Document).documentElement != null)
      throw new DOMException("HIERARCHY_REQUEST_ERR");
    
    if (childNodes == null)
      childNodes = new List<Node>();
    
    if (newChild.parentNode != null)
      newChild.parentNode.removeChild(newChild);
    
    newChild.parentNode = this;
    if (nodeType == DOCUMENT_NODE && newChild.nodeType == ELEMENT_NODE)
      (this as Document).documentElement = newChild;
    
    newChild.nextSibling = null;
    
    if (firstChild == null) {
      newChild.previousSibling = null;
      firstChild = newChild;
      lastChild = newChild;
    } else {
      lastChild.nextSibling = newChild;
      newChild.previousSibling = lastChild;
      lastChild = newChild;
    }
    
    childNodes.add(newChild);
    return(newChild);
  }
  
  bool hasChildNodes() {
    return(firstChild != null);
  }
  
  Node cloneNode(bool deep);
  
  bool hasAttributes() {
    return(attributes != null && attributes.length > 0);
  }
  
  String lookupPrefix(String namespaceURI) {
    // based on https://developer.mozilla.org/en-US/docs/Code_snippets/LookupPrefix
    if (namespaceURI == null || namespaceURI == '') {
      return(null);
    }
    switch (nodeType) {
        case ELEMENT_NODE:
            return(_lookupNamespacePrefix(namespaceURI));
        case DOCUMENT_NODE:
            return(((this as Document).documentElement as ElementImpl)._lookupNamespacePrefix(namespaceURI));
        case ENTITY_NODE:
        case NOTATION_NODE:
        case DOCUMENT_FRAGMENT_NODE:
        case DOCUMENT_TYPE_NODE:
            return(null);
        case ATTRIBUTE_NODE:
            if ((this as Attr).ownerElement != null) {
                return(((this as Attr).ownerElement as ElementImpl)._lookupNamespacePrefix(namespaceURI));
            }
            return null;
        default:
            if (parentNode != null) {
                return((parentNode as ElementImpl)._lookupNamespacePrefix(namespaceURI));
            }
            return(null);
     }
  }
  
  _lookupNamespacePrefix(final String namespaceURI) {
      final RegExp xmlnsPattern = new RegExp(r"/^xmlns:(.*)$/");
      if (namespaceURI != null && this.namespaceURI == namespaceURI &&
              prefix != null && lookupNamespaceURI(prefix) == namespaceURI) {
          return(prefix);
      }
      if (attributes != null) {
          for (Attr att in attributes.values) {
              if (xmlnsPattern.hasMatch(att.name) && att.value == namespaceURI &&
                  lookupNamespaceURI(att.localName) == namespaceURI) {
                return(localName);
              }
          }
      }
      if (parentNode != null) {
          return((parentNode as ElementImpl)._lookupNamespacePrefix(namespaceURI));
      }
      return null;
  }
  
  String lookupNamespaceURI(String prefix) {
    // cf http://www.w3.org/TR/DOM-Level-3-Core/namespaces-algorithms.html#lookupNamespaceURIAlgo
    switch (nodeType) {
      case ELEMENT_NODE:
        if (namespaceURI != null && this.prefix == prefix)  {
          return(namespaceURI);
        }
        if (attributes != null) {
          for (Attr att in attributes.values) {
            if (att.prefix == 'xmlns' && att.localName == prefix) {
              if (att.value != null && att.value != '') {
                return(att.value);
              }
              return(null);
            } else if (att.localName == 'xmlns' && prefix == null) {
              if (att.value != null && att.value != '') {
                return(att.value);
              }
              return(null);
            }
          }
        }
        if (parentNode != null && parentNode.nodeType != DOCUMENT_NODE) {
          return parentNode.lookupNamespaceURI(prefix);
        }
        return null;
      case DOCUMENT_NODE:
        return((this as Document).documentElement.lookupNamespaceURI(prefix));
      case ENTITY_NODE:
      case NOTATION_NODE:
      case DOCUMENT_TYPE_NODE:
      case DOCUMENT_FRAGMENT_NODE:
        return(null);
      case ATTRIBUTE_NODE:
        if (parentNode != null)
          return(parentNode.lookupNamespaceURI(prefix));
        return(null);
      default:
        if (parentNode != null)
          return(parentNode.lookupNamespaceURI(prefix));
        return(null);
    }
  }
  
  _checkNewChildValid(Node newChild) { // throws DOMException
    if (nodeType != DOCUMENT_NODE && ownerDocument != newChild.ownerDocument)
      throw new DOMException("WRONG_DOCUMENT_ERR");
    
    if (nodeType == DOCUMENT_NODE && this != newChild.ownerDocument)
      throw new DOMException("WRONG_DOCUMENT_ERR");
    
    if (nodeType != ELEMENT_NODE && nodeType != DOCUMENT_NODE)
      throw new DOMException("HIERARCHY_REQUEST_ERR");
    
    if (newChild.nodeType == DOCUMENT_NODE)
      throw new DOMException("HIERARCHY_REQUEST_ERR");
    
    if (nodeType != DOCUMENT_NODE && newChild.nodeType == ATTRIBUTE_NODE)
      throw new DOMException("HIERARCHY_REQUEST_ERR");
    
    // etc...
    
    Node ancestor = this;
    while (ancestor != null) {
      if (newChild == ancestor)
        throw new DOMException("HIERARCHY_REQUEST_ERR");
      ancestor = ancestor.parentNode;
    }
  }
  
  /// escapes XML character entities for serialization
  static String _escape(String s) {
    s = s.replaceAll('&', '&');
    s = s.replaceAll('"', '"');
    //s = s.replaceAll("'", ''');
    s = s.replaceAll('<', '<');
    s = s.replaceAll('>', '>');
    return(s);
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/processing_instruction.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/processing_instruction.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

abstract class ProcessingInstruction extends Node {
  String target;
  String data;
}


Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/processing_instruction_impl.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/processing_instruction_impl.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

class ProcessingInstructionImpl extends NodeImpl implements ProcessingInstruction {
  String target;
  String data;
  
  ProcessingInstructionImpl.clone(final ProcessingInstruction pi) {
    target = pi.target;
    data = pi.data;
    
    nodeName = pi.nodeName;
    nodeValue = pi.nodeValue;
    nodeType = pi.nodeType;
    parentNode = null;
    childNodes = null;
    firstChild = null;
    lastChild = null;
    previousSibling = null;
    nextSibling = null;
    attributes = null;
    ownerDocument = pi.ownerDocument;
    namespaceURI = pi.namespaceURI;
    prefix = null;
    localName = null;
  }
  
  ProcessingInstructionImpl(final Document doc, final String target, final String data) {
    this.target = target;
    this.data = data;
    
    nodeName = target;
    nodeValue = data;
    nodeType = Node.PROCESSING_INSTRUCTION_NODE;
    parentNode = null;
    childNodes = null;
    firstChild = null;
    lastChild = null;
    previousSibling = null;
    nextSibling = null;
    attributes = null;
    ownerDocument = doc;
    namespaceURI = null;
    prefix = null;
    localName = null;
  }
  
  /*
  ProcessingInstructionImpl.fromDH(final Document doc, final h.ProcessingInstruction pi) {
    target = pi.target;
    data = pi.data;
    
    nodeName = target;
    nodeValue = pi.nodeValue;
    nodeType = Node.PROCESSING_INSTRUCTION_NODE;
    parentNode = null;
    childNodes = null;
    firstChild = null;
    lastChild = null;
    previousSibling = null;
    nextSibling = null;
    attributes = null;
    ownerDocument = doc;
    namespaceURI = null;
    prefix = null;
    localName = null;
  }
  */
  
  Node cloneNode(bool deep) {
    return(new ProcessingInstructionImpl.clone(this));
  }
  
  String toString() {
    return("<?$target $data?>");
  }
}


Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/text.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/text.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

abstract class Text extends Node {
  
  /*
  Text splitText(int offset); // throws DOMException
  bool isElementContentWhitespace;
  String wholeText;
  Text replaceWholeText(String content); // throws DOMException
  */
}


Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/text_impl.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/text_impl.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

class TextImpl extends NodeImpl implements Text {
  
  TextImpl.clone(final Text txt) {
    nodeName = txt.nodeName;
    nodeValue = txt.nodeValue;
    nodeType = txt.nodeType;
    parentNode = null;
    childNodes = null;
    firstChild = null;
    lastChild = null;
    previousSibling = null;
    nextSibling = null;
    attributes = null;
    ownerDocument = txt.ownerDocument;
    namespaceURI = txt.namespaceURI;
    prefix = null;
    localName = null;
  }
  
  TextImpl(final Document doc, final String data) {
    nodeName = "#text";
    nodeValue = data;
    nodeType = Node.TEXT_NODE;
    parentNode = null;
    childNodes = null;
    firstChild = null;
    lastChild = null;
    previousSibling = null;
    nextSibling = null;
    attributes = null;
    ownerDocument = doc;
    namespaceURI = null;
    prefix = null;
    localName = null;
  }
  
  /*
  TextImpl.fromDH(final Document doc, final h.Text txt) {
    nodeName = "#text";
    nodeValue = txt.nodeValue;
    nodeType = Node.TEXT_NODE;
    parentNode = null;
    childNodes = null;
    firstChild = null;
    lastChild = null;
    previousSibling = null;
    nextSibling = null;
    attributes = null;
    ownerDocument = doc;
    namespaceURI = null;
    prefix = null;
    localName = null;
  }
  */
  
  Node cloneNode(bool deep) {
    return(new TextImpl.clone(this));
  }
  
  String toString() {
    return(NodeImpl._escape(nodeValue));
  }
}


Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/xmldom.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/xmldom.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

/**
 * XML DOM API based on the [W3C recommandation](http://www.w3.org/TR/DOM-Level-3-Core/core.html)
 * but not all features are implemented.
 * A document can be parsed with [DOMParser].
 */
library xmldom;

import 'dart:async';
import 'dart:collection';
import 'dart:html' as h;
import 'dart:math';

part 'attr.dart';
part 'cdata_section.dart';
part 'comment.dart';
part 'document.dart';
part 'document_fragment.dart';
part 'document_type.dart';
part 'dom_implementation.dart';
part 'element.dart';
part 'entity_reference.dart';
part 'node.dart';
part 'processing_instruction.dart';
part 'text.dart';

part 'attr_impl.dart';
part 'cdata_section_impl.dart';
part 'comment_impl.dart';
part 'document_impl.dart';
part 'document_fragment_impl.dart';
part 'document_type_impl.dart';
part 'dom_implementation_impl.dart';
part 'element_impl.dart';
part 'entity_reference_impl.dart';
part 'node_impl.dart';
part 'processing_instruction_impl.dart';
part 'text_impl.dart';

part 'dom_exception.dart';
part 'dom_parser.dart';

part 'parser/engine.dart';
part 'parser/match_result.dart';
part 'parser/state_change.dart';
part 'parser/state_condition.dart';
part 'parser/token.dart';
part 'parser/token_char.dart';
part 'parser/token_sequence.dart';
part 'parser/token_id.dart';
part 'parser/token_item.dart';
part 'parser/token_choice.dart';
part 'parser/token_repeat.dart';
part 'parser/token_rule.dart';
part 'parser/xml_parser.dart';

Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/parser/engine.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/parser/engine.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

/**
 * Text parser engine. Parsing returns a list of token created from an input string or list of tokens
 * by applying a list of rules.
 * The rules can use states (variables) as conditions to execute, and change the states when they are applied.
 * A character in an input string is the equivalent of a token in an input list of tokens:
 * characters could be turned into tokens to simplify the implementation if we didn't worry about performance.
 */
class Engine {
  
  List<TokenRule> rules;
  HashMap<String, bool> states;
  
  Engine(this.rules) {
    for (TokenRule rule in rules)
      rule.engine = this;
  }
  
  List<Token> parseString(String s) {
    List<Token> result = new List<Token>();
    states = new HashMap<String, bool>();
    int pos = 0;
    while (pos < s.length) {
      int pos1 = pos;
      for (TokenRule rule in rules) {
        MatchResult match = rule.evaluateString(s, pos);
        if (match == null)
          continue;
        pos += match.nbMatched;
        if (match.tokens.length > 0) {
          assert(match.nbMatched != 0); // there is a match but no token is consumed by the rule: this could lead to an endless loop
          result.addAll(match.tokens);
          break; // continue with first rule
        }
      }
      if (pos1 == pos) {
        // TODO: add line information
        throw new DOMException("parser blocking at character $pos: ${s.substring(pos, min(pos + 10, s.length))}");
      }
    }
    return(result);
  }
  
  List<Token> parseTokens(List<Token> tokens) {
    List<Token> result = new List<Token>();
    states = new HashMap<String, bool>();
    int pos = 0;
    while (pos < tokens.length) {
      int pos1 = pos;
      for (TokenRule rule in rules) {
        MatchResult match = rule.evaluateTokens(tokens, pos);
        if (match == null)
          continue;
        pos += match.nbMatched;
        if (match.tokens.length > 0) {
          assert(match.nbMatched != 0); // there is a match but no token is consumed by the rule: this is wrong
          result.addAll(match.tokens);
          break; // continue with first rule
        }
      }
      if (pos1 == pos) {
        // TODO: add line information
        throw new DOMException("parser blocking at character ${tokens[pos].position}");
      }
    }
    return(result);
  }
  
  bool checkConditions(TokenRule rule) {
    if (rule.conditions == null)
      return(true);
    bool stop = false;
    for (StateCondition condition in rule.conditions) {
      bool current = states[condition.name];
      if (current == null)
        current = false; // false by default
      if (current != condition.value)
        return(false);
    }
    return(true);
  }
  
  void changeStates(TokenRule rule) {
    if (rule.changes == null)
      return;
    for (StateChange change in rule.changes) {
      states[change.name] = change.value; 
    }
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/parser/match_result.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/parser/match_result.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

class MatchResult {
  
  int nbMatched;
  String characters;
  List<Token> tokens;
  
  
  MatchResult.ch(this.nbMatched, this.characters);
  
  MatchResult.tk(this.nbMatched, this.tokens);
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/parser/state_change.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/parser/state_change.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

class StateChange {
  String name;
  bool value;
  
  StateChange(this.name, this.value);
}

Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/parser/state_condition.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/parser/state_condition.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

class StateCondition {
  String name;
  bool value;
  
  StateCondition(this.name, this.value);
}

Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/parser/token.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/parser/token.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

class Token {
  
  String id;
  String matchedString;
  List<Token> matchedTokens;
  Object data;
  int position;
  
  
  Token.characters(this.id, this.matchedString, this.position);
  
  Token.tokens(this.id, this.matchedTokens, this.position);
  
  String toString() {
    StringBuffer sb = new StringBuffer();
    sb.write("[$id");
    if (matchedString != null)
      sb.write(" $matchedString");
    else {
      for (Token token in matchedTokens) {
        sb.write(' ');
        sb.write(token);
      }
    }
    sb.write(']');
    return(sb.toString());
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/parser/token_char.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/parser/token_char.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

class TokenChar extends TokenItem {
  
  String character = null;
  bool isdigit = false;
  bool isletter = false;
  bool isany = false;
  List<String> exceptions = null;
  
  TokenChar(this.character);
  
  TokenChar.digit() {
    isdigit = true;
  }
  
  TokenChar.letter() {
    isletter = true;
  }
  
  TokenChar.any() {
    isany = true;
  }
  
  TokenChar.except(this.exceptions);
  
  MatchResult evaluateString(String doc, int pos) {
    String c = doc[pos];
    
    if (character != null) {
      if (c == character)
        return(new MatchResult.ch(1, c));
    } else if (isdigit) {
      if ('0123456789'.contains(c)) {
        return(new MatchResult.ch(1, c));
      }
    } else if (isletter) {
      // FIXME: more letters are acceptable
      if ('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.contains(c))
        return(new MatchResult.ch(1, c));
    } else if (isany) {
      return(new MatchResult.ch(1, c));
    } else if (exceptions != null) {
      for (String s in exceptions) {
        if (pos + s.length <= doc.length && s == doc.substring(pos, pos + s.length))
          return(null);
      }
      return(new MatchResult.ch(1, c));
    }
    
    return(null);
  }
  
  MatchResult evaluateTokens(List<Token> tokens, int pos) {
    return(null);
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/parser/token_choice.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/parser/token_choice.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

class TokenChoice extends TokenItem {
  
  List<TokenItem> items;
  
  TokenChoice(this.items);
  
  MatchResult evaluateString(String doc, int pos) {
    for (TokenItem item in items) {
      MatchResult match = item.evaluateString(doc, pos);
      if (match != null)
        return(match);
    }
    return(null);
  }
  
  MatchResult evaluateTokens(List<Token> tokens, int pos) {
    for (TokenItem item in items) {
      MatchResult match = item.evaluateTokens(tokens, pos);
      if (match != null)
        return(match);
    }
    return(null);
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/parser/token_id.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/parser/token_id.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

class TokenId extends TokenItem {
  
  String id;
  
  TokenId(this.id);
  
  MatchResult evaluateString(String doc, int pos) {
    return(null);
  }
  
  MatchResult evaluateTokens(List<Token> tokens, int pos) {
    if (tokens[pos].id == id)
      return(new MatchResult.tk(1, [tokens[pos]]));
    else
      return(null);
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/parser/token_item.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/parser/token_item.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

abstract class TokenItem {
  
  MatchResult evaluateString(String doc, int pos);
  
  MatchResult evaluateTokens(List<Token> tokens, int pos);
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/parser/token_repeat.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/parser/token_repeat.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

class TokenRepeat extends TokenItem {
  
  static const int ZERO_OR_ONE = 0;
  static const int ZERO_OR_MORE = 1;
  static const int ONE_OR_MORE = 2;
  int repeat;
  TokenItem item;
  
  TokenRepeat.zeroOrOne(this.item) {
    repeat = ZERO_OR_ONE;
  }
  
  TokenRepeat.zeroOrMore(this.item) {
    repeat = ZERO_OR_MORE;
  }
  
  TokenRepeat.oneOrMore(this.item) {
    repeat = ONE_OR_MORE;
  }
  
  MatchResult evaluateString(String doc, int pos) {
    if (repeat == ZERO_OR_ONE) {
      MatchResult match = item.evaluateString(doc, pos);
      if (match == null)
        return(new MatchResult.ch(0, ''));
      else
        return(match);
    }
    StringBuffer sb = null;
    int i = pos;
    while (i < doc.length) {
      MatchResult match = item.evaluateString(doc, i);
      if (match == null)
        break;
      if (sb == null)
        sb = new StringBuffer();
      sb.write(match.characters);
      i = i + match.nbMatched;
    }
    int nbMatched = i - pos;
    if (nbMatched > 0 || repeat == ZERO_OR_MORE) {
      String s;
      if (sb != null)
        s = sb.toString();
      else
        s = '';
      return(new MatchResult.ch(nbMatched, s));
    } else
      return(null);
  }
  
  MatchResult evaluateTokens(List<Token> tokens, int pos) {
    if (repeat == ZERO_OR_ONE) {
      MatchResult match = item.evaluateTokens(tokens, pos);
      if (match == null)
        return(new MatchResult.tk(0, new List<Token>()));
      else
        return(match);
    }
    List<Token> resultTokens = new List<Token>();
    int i = pos;
    while (i < tokens.length) {
      MatchResult match = item.evaluateTokens(tokens, i);
      if (match == null)
        break;
      resultTokens.addAll(match.tokens);
      i = i + match.nbMatched;
    }
    int nbMatched = i - pos;
    if (nbMatched > 0 || repeat == ZERO_OR_MORE)
      return(new MatchResult.tk(nbMatched, resultTokens));
    else
      return(null);
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/parser/token_rule.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/parser/token_rule.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

typedef void TokenAction(Token);

class TokenRule extends TokenItem {
  String id;
  List<StateCondition> conditions;
  List<StateChange> changes;
  TokenItem content;
  /** Ignore the tokens matched by this rule */
  bool ignore;
  TokenAction action;
  Engine engine; // defined in Engine constructor
  
  TokenRule(this.id, this.content, {this.conditions:null, this.changes:null, this.ignore:false, this.action:null});
  
  MatchResult evaluateString(String doc, int pos) {
    if (!engine.checkConditions(this))
      return(null); // next rule
    MatchResult match = content.evaluateString(doc, pos);
    if (match == null)
      return(null);
    Token token = new Token.characters(id, match.characters, pos);
    if (action != null)
      action(token);
    engine.changeStates(this);
    List<Token> resultTokens;
    if (ignore)
      resultTokens = new List<Token>();
    else
      resultTokens = [token];
    MatchResult result = new MatchResult.tk(match.nbMatched, resultTokens);
    if (!ignore)
      result.characters = token.matchedString;
    return(result);
  }
  
  MatchResult evaluateTokens(List<Token> tokens, int pos) {
    if (!engine.checkConditions(this))
      return(null);
    MatchResult match = content.evaluateTokens(tokens, pos);
    if (match == null)
      return(null);
    Token token = new Token.tokens(id, match.tokens, match.tokens[0].position);
    if (action != null)
      action(token);
    engine.changeStates(this);
    List<Token> resultTokens;
    if (ignore)
      resultTokens = new List<Token>();
    else
      resultTokens = [token];
    MatchResult result = new MatchResult.tk(match.nbMatched, resultTokens);
    return(result);
  }
  
  String toString() {
    return(id);
  }
}

Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/parser/token_sequence.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/parser/token_sequence.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

class TokenSequence extends TokenItem {
  
  List<TokenItem> items;
  
  TokenSequence(this.items);
  
  TokenSequence.string(String s) {
    items = new List<TokenItem>();
    for (int i=0; i<s.length; i++)
      items.add(new TokenChar(s[i]));
  }
  
  MatchResult evaluateString(String doc, int pos) {
    StringBuffer sb = null;
    int i = pos;
    for (TokenItem item in items) {
      if (i >= doc.length)
        return(null);
      MatchResult match = item.evaluateString(doc, i);
      if (match == null)
        return(null);
      if (sb == null)
        sb = new StringBuffer();
      sb.write(match.characters);
      i = i + match.nbMatched;
    }
    int nbMatched = i - pos;
    String s;
    if (sb != null)
      s = sb.toString();
    else
      s = '';
    return(new MatchResult.ch(nbMatched, s));
  }
  
  MatchResult evaluateTokens(List<Token> tokens, int pos) {
    List<Token> resultTokens = new List<Token>();
    int i = pos;
    for (TokenItem item in items) {
      if (i >= tokens.length)
        return(null);
      MatchResult match = item.evaluateTokens(tokens, i);
      if (match == null)
        return(null);
      resultTokens.addAll(match.tokens);
      i = i + match.nbMatched;
    }
    int nbMatched = i - pos;
    return(new MatchResult.tk(nbMatched, resultTokens));
  }
  
}

Index: modules/damieng/graphical_editor/daxe/lib/src/xmldom/parser/xml_parser.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/xmldom/parser/xml_parser.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of xmldom;

/**
 * DOM XML parser.
 * It is not feature-complete, and does not report errors well for documents that are not well-formed.
 */
class XMLParser {
  
  // The implementation was inspired by http://www.antlr.org/wiki/display/ANTLR3/Parsing+XML
  
  List<TokenRule> lexRules;
  List<TokenRule> xmlRules;
  Document doc;
  
  XMLParser() {
    _initRules();
  }
  
  _initRules() {
    lexRules = new List<TokenRule>();
    StateCondition inTag = new StateCondition('in_tag', true);
    lexRules.add(new TokenRule('CDATA_SECTION_OPEN', new TokenSequence.string('<![CDATA['),
        conditions: [new StateCondition('in_cdata_section', false)],
        changes: [new StateChange('in_cdata_section', true)]));
    lexRules.add(new TokenRule('CDATA_SECTION_DATA', new TokenRepeat.oneOrMore(new TokenChar.except([']]>'])),
        conditions: [new StateCondition('in_cdata_section', true)]));
    lexRules.add(new TokenRule('CDATA_SECTION_CLOSE', new TokenSequence.string(']]>'),
        conditions: [new StateCondition('in_cdata_section', true)],
        changes: [new StateChange('in_cdata_section', false)]));
    lexRules.add(new TokenRule('COMMENT_OPEN', new TokenSequence.string('<!--'),
        changes: [new StateChange('in_comment', true)]));
    lexRules.add(new TokenRule('COMMENT_CLOSE', new TokenSequence.string('-->'),
        conditions: [new StateCondition('in_comment', true)],
        changes: [new StateChange('in_comment', false)]));
    lexRules.add(new TokenRule('COMMENT_DATA', new TokenRepeat.oneOrMore(new TokenChar.except(['-->'])),
        conditions: [new StateCondition('in_comment', true)]));
    lexRules.add(new TokenRule('DOC_DECL_OPEN', new TokenSequence.string('<?xml'),
        changes: [new StateChange('in_tag', true), new StateChange('in_decl', true)]));
    lexRules.add(new TokenRule('DOC_DECL_CLOSE', new TokenSequence.string('?>'),
        conditions: [new StateCondition('in_decl', true)],
        changes: [new StateChange('in_tag', false), new StateChange('in_decl', false)]));
    lexRules.add(new TokenRule('DOCTYPE_OPEN', new TokenSequence.string('<!DOCTYPE'),
        changes: [new StateChange('in_tag', true), new StateChange('in_doctype', true)]));
    lexRules.add(new TokenRule('DOCTYPE_CLOSE', new TokenSequence.string('>'),
        conditions: [new StateCondition('in_doctype', true)],
        changes: [new StateChange('in_tag', false), new StateChange('in_doctype', false)]));
    lexRules.add(new TokenRule('PI_OPEN', new TokenSequence.string('<?'),
        changes: [new StateChange('in_pi', true)]));
    TokenItem firstChar = new TokenChoice([
      new TokenChar.letter(),
      new TokenChar('_'),
      new TokenChar(':')
    ]);
    TokenItem nameChar = new TokenChoice([
      new TokenChar.letter(),
      new TokenChar.digit(),
      new TokenChar('.'),
      new TokenChar('-'),
      new TokenChar('_'),
      new TokenChar(':')
    ]);
    lexRules.add(new TokenRule('PI_TARGET', new TokenSequence([
        firstChar,
        new TokenRepeat.zeroOrMore(nameChar)
        ]),
        conditions: [new StateCondition('in_pi', true), new StateCondition('pi_after_target', false)],
        changes: [new StateChange('pi_after_target', true)]));
    lexRules.add(new TokenRule('PI_DATA', new TokenRepeat.oneOrMore(new TokenChar.except(['?>'])),
        conditions: [new StateCondition('pi_after_target', true)]));
    lexRules.add(new TokenRule('PI_CLOSE', new TokenSequence.string('?>'),
        conditions: [new StateCondition('in_pi', true)],
        changes: [new StateChange('in_pi', false), new StateChange('pi_after_target', false)]));
    lexRules.add(new TokenRule('TAG_END_OPEN', new TokenSequence.string('</'),
        changes: [new StateChange('in_tag', true)]));
    lexRules.add(new TokenRule('TAG_START_OPEN', new TokenChar('<'),
        changes: [new StateChange('in_tag', true)]));
    lexRules.add(new TokenRule('TAG_CLOSE', new TokenChar('>'),
        conditions: [inTag],
        changes: [new StateChange('in_tag', false)]));
    lexRules.add(new TokenRule('TAG_EMPTY_CLOSE', new TokenSequence.string('/>'),
        conditions: [inTag],
        changes: [new StateChange('in_tag', false)]));
    lexRules.add(new TokenRule('ATTR_EQ', new TokenChar('='),
        conditions: [inTag]));
    TokenRule entityRef = new TokenRule('ENTITY_REF', new TokenSequence([
      new TokenChar('&'),
      new TokenRepeat.oneOrMore(
        new TokenChoice([
          new TokenChar('#'),
          new TokenChar.digit(),
          new TokenChar.letter()
        ])
      ),
      new TokenChar(';')
    ]), action: (Token token) {
      String data = token.matchedString;
      data = data.substring(1, data.length - 1);
      if (data.startsWith('#')) {
        int num;
        if (data[1] == 'x') {
          data = data.substring(2);
          num = int.parse(data, radix: 16);
        } else {
          data = data.substring(1);
          num = int.parse(data);
        }
        String s = new String.fromCharCode(num);
        token.matchedString = new String.fromCharCode(num);
      } else {
        String s = null;
        if (data == 'amp')
          s = '&';
        else if (data == 'lt')
          s = '<';
        else if (data == 'gt')
          s = '>';
        else if (data == 'apos')
          s = "'";
        else if (data == 'quot')
          s = '"';
        if (s != null) {
          token.matchedString = s;
        } else {
          // unknown named entity: we can't replace it here
        }
      }
    });
    lexRules.add(entityRef);
    lexRules.add(new TokenRule('ATTR_VALUE', new TokenChoice([
        new TokenSequence([
          new TokenChar('"'),
          new TokenRepeat.zeroOrMore(
            new TokenChoice([
              new TokenChar.except(['"', '&', '<']),
              entityRef
            ])
          ),
          new TokenChar('"')
        ]),
        new TokenSequence([
          new TokenChar("'"),
          new TokenRepeat.zeroOrMore(
            new TokenChoice([
              new TokenChar.except(["'", '&', '<']),
              entityRef
            ])
          ),
          new TokenChar("'")
        ])
      ]),
      conditions: [inTag]));
    lexRules.add(new TokenRule('PCDATA', new TokenRepeat.oneOrMore(new TokenChar.except(['<', '&'])),
        conditions: [new StateCondition('in_tag', false)]));
    lexRules.add(new TokenRule('GENERIC_ID', new TokenSequence([
        firstChar,
        new TokenRepeat.zeroOrMore(nameChar)
      ]),
      conditions: [inTag]));
    lexRules.add(new TokenRule('WHITE', new TokenChoice([
        new TokenChar(' '),
        new TokenChar('\n'),
        new TokenChar('\r'),
        new TokenChar('\t')
      ]),
      conditions: [inTag],
      ignore: true));
    
    
    xmlRules = new List<TokenRule>();
    TokenRule attributeRule = new TokenRule('ATTRIBUTE', new TokenSequence([
        new TokenId('GENERIC_ID'),
        new TokenId('ATTR_EQ'),
        new TokenId('ATTR_VALUE')
      ]), action: (Token token) {
        String name = token.matchedTokens[0].matchedString;
        String value = token.matchedTokens[2].matchedString;
        value = value.substring(1, value.length - 1);
        Attr attr = new AttrImpl.NS(doc, null, name);
        attr.value = value;
        token.data = attr;
      }
    );
    xmlRules.add(new TokenRule('DOC_DECL', new TokenSequence([
      new TokenId('DOC_DECL_OPEN'),
      new TokenRepeat.zeroOrMore(attributeRule),
      new TokenId('DOC_DECL_CLOSE')
    ]), action: (Token token) {
      int nbAttributes = token.matchedTokens.length - 2;
      if (nbAttributes > 0) {
        for (int i= 1; i < token.matchedTokens.length - 1; i++) {
          Object data = token.matchedTokens[i].data;
          if (data is Attr) {
            Attr attr = data;
            if (attr.name == 'version')
              doc.xmlVersion = attr.value;
            else if (attr.name == 'encoding') {
             doc.xmlEncoding = attr.value;
             doc.inputEncoding = attr.value;
            }
          }
        }
      }
    }));
    xmlRules.add(new TokenRule('DOCTYPE', new TokenSequence([
      new TokenId('DOCTYPE_OPEN'),
      new TokenRepeat.oneOrMore(
        new TokenChoice([
          new TokenId('GENERIC_ID'),
          new TokenId('ATTR_VALUE')
        ])
      ),
      new TokenId('DOCTYPE_CLOSE')
    ]), action: (Token token) {
      String name = token.matchedTokens[1].matchedString;
      String publicId = null;
      String systemId = null;
      for (int i=2; i<token.matchedTokens.length - 1; i++) {
        if (token.matchedTokens[i].id == 'GENERIC_ID' &&
            token.matchedTokens[i].matchedString == 'PUBLIC' &&
            i < token.matchedTokens.length - 2 &&
            token.matchedTokens[i+1].id == 'ATTR_VALUE') {
          publicId = token.matchedTokens[i+1].matchedString;
          publicId = publicId.substring(1, systemId.length - 1);
        } else if (token.matchedTokens[i].id == 'GENERIC_ID' &&
            token.matchedTokens[i].matchedString == 'SYSTEM' &&
            i < token.matchedTokens.length - 2 &&
            token.matchedTokens[i+1].id == 'ATTR_VALUE') {
          systemId = token.matchedTokens[i+1].matchedString;
          systemId = systemId.substring(1, systemId.length - 1);
        }
        // TODO: better support for DOCTYPE
      }
      DocumentType doctype = new DocumentTypeImpl(name, publicId, systemId);
      doctype.ownerDocument = doc;
      token.data = doctype;
    }));
    xmlRules.add(new TokenRule('OUTSIDE_ROOT', new TokenId('PCDATA'),
        ignore: true));
    TokenRule startTagRule = new TokenRule('START_TAG', new TokenSequence([
      new TokenId('TAG_START_OPEN'),
      new TokenId('GENERIC_ID'),
      new TokenRepeat.zeroOrMore(attributeRule),
      new TokenId('TAG_CLOSE')
    ]));
    TokenRule endTagRule = new TokenRule('END_TAG', new TokenSequence([
      new TokenId('TAG_END_OPEN'),
      new TokenId('GENERIC_ID'),
      new TokenId('TAG_CLOSE')
    ]));
    TokenRule emptyElementRule = new TokenRule('EMPTY_ELEMENT', new TokenSequence([
      new TokenId('TAG_START_OPEN'),
      new TokenId('GENERIC_ID'),
      new TokenRepeat.zeroOrMore(attributeRule),
      new TokenId('TAG_EMPTY_CLOSE')
    ]));
    TokenRule commentRule = new TokenRule('COMMENT', new TokenSequence([
      new TokenId('COMMENT_OPEN'),
      new TokenRepeat.zeroOrOne(new TokenId('COMMENT_DATA')),
      new TokenId('COMMENT_CLOSE')
    ]), action: (Token token) {
      String data;
      if (token.matchedTokens.length == 3)
        data = token.matchedTokens[1].matchedString;
      else
        data = '';
      token.data = new CommentImpl(doc, data);
    });
    TokenRule entityRefRule = new TokenRule('ENTITY_REF', new TokenSequence([
      new TokenId('ENTITY_REF')
    ]), action: (Token token) {
      String data = token.matchedTokens[0].matchedString;
      if (data.startsWith('&') && data.length > 1) {
        data = data.substring(1, data.length - 1);
        EntityReferenceImpl entityRef = new EntityReferenceImpl(doc, data);
        token.data = entityRef;
      } else {
        token.data = new TextImpl(doc, data);
      }
    });
    TokenRule cdataSectionRule = new TokenRule('CDATA', new TokenSequence([
      new TokenId('CDATA_SECTION_OPEN'),
      new TokenRepeat.zeroOrOne(new TokenId('CDATA_SECTION_DATA')),
      new TokenId('CDATA_SECTION_CLOSE')
    ]), action: (Token token) {
      String data;
      if (token.matchedTokens.length == 3)
        data = token.matchedTokens[1].matchedString;
      else
        data = '';
      token.data = new CDATASectionImpl(doc, data);
    });
    TokenRule piRule = new TokenRule('PI', new TokenSequence([
      new TokenId('PI_OPEN'),
      new TokenId('PI_TARGET'),
      new TokenRepeat.zeroOrOne(new TokenId('PI_DATA')),
      new TokenId('PI_CLOSE')
    ]), action: (Token token) {
      String target = token.matchedTokens[1].matchedString;
      String data;
      if (token.matchedTokens.length == 4)
        data = token.matchedTokens[2].matchedString.replaceAll(new RegExp(r"^\s+"), '');
      else
        data = '';
      token.data = new ProcessingInstructionImpl(doc, target, data);
    });
    TokenRule elementRule = new TokenRule('ELEMENT', null, action: (Token token) {
      bool empty = (token.matchedTokens[0].id == 'EMPTY_ELEMENT');
      Token startTagToken = token.matchedTokens[0];
      String name = startTagToken.matchedTokens[1].matchedString;
      if (!empty) {
        // check that the end tag matches the start tag
        // (this could be disabled for speed, assuming documents are well-formed)
        Token endTagToken = token.matchedTokens[token.matchedTokens.length - 1];
        String endName = endTagToken.matchedTokens[1].matchedString;
        if (endName != name)
          throw new Exception("End tag not matching start tag: $endName != $name");
      }
      Element el = new ElementImpl.NS(doc, null, name);
      int nbAttributes = startTagToken.matchedTokens.length - 3;
      if (nbAttributes > 0) {
        for (int i= 2; i < startTagToken.matchedTokens.length - 1; i++) {
          Object data = startTagToken.matchedTokens[i].data;
          if (data is Attr)
            el.setAttributeNode(data);
        }
      }
      if (!empty) {
        int nbChildren = token.matchedTokens.length - 2;
        if (nbChildren > 0) {
          for (int i= 1; i < token.matchedTokens.length - 1; i++) {
            Token tokeni = token.matchedTokens[i]; 
            if (tokeni.id == 'PCDATA') {
              Text text = new TextImpl(doc, tokeni.matchedString);
              el.appendChild(text);
            } else {
              Object data = tokeni.data;
              if (data is Node)
                el.appendChild(data);
            }
          }
          // normalize text nodes (but not CDATA sections)
          for (Node n = el.firstChild; n != null; n = n.nextSibling) {
            if (n.nodeType == Node.TEXT_NODE) {
              Node n2 = n.nextSibling;
              while (n2 != null && n2.nodeType == Node.TEXT_NODE) {
                n.nodeValue += n2.nodeValue;
                el.removeChild(n2);
                n2 = n.nextSibling;
              }
            }
          }
        }
      }
      token.data = el;
    });
    elementRule.content = new TokenChoice([
      new TokenSequence([
        startTagRule,
        new TokenRepeat.zeroOrMore(
          new TokenChoice([
            elementRule,
            commentRule,
            new TokenId('PCDATA'),
            entityRefRule,
            cdataSectionRule,
            piRule
          ])
        ),
        endTagRule
      ]),
      emptyElementRule
    ]);
    xmlRules.add(elementRule);
    // we need to add these rules so that they get associated with the engine:
    xmlRules.add(attributeRule);
    xmlRules.add(startTagRule);
    xmlRules.add(endTagRule);
    xmlRules.add(emptyElementRule);
    xmlRules.add(commentRule);
    xmlRules.add(entityRefRule);
    xmlRules.add(cdataSectionRule);
    xmlRules.add(piRule);
  }
  
  Document parseString(String s) {
    s = s.replaceAll('\r\n', '\n');
    DOMImplementation implementation = new DOMImplementationImpl();
    doc = new DocumentImpl(implementation, null, null, null);
    
    Engine engine = new Engine(lexRules);
    List<Token> tokens = engine.parseString(s);
    
    engine = new Engine(xmlRules);
    tokens = engine.parseTokens(tokens);
    for (Token token in tokens) {
      Object data = token.data;
      if (data is DocumentType)
        doc.doctype = data;
      else if (data is Node)
        doc.appendChild(data);
    }
    if (doc.documentElement != null)
      _fixNamespaces(doc.documentElement);
    return(doc);
  }
  
  void _fixNamespaces(Element el) {
    if (el.attributes != null) {
      for (Attr attr in el.attributes.values) {
        if (attr.name == 'xmlns' ||
            (attr.prefix == 'xmlns' && attr.localName == el.prefix)) {
          el.namespaceURI = attr.value;
          break;
        }
      }
    }
    if (el.namespaceURI == null && el.parentNode != null)
      el.namespaceURI = el.parentNode.lookupNamespaceURI(el.prefix);
    for (Node n = el.firstChild; n != null; n = n.nextSibling) {
      if (n is Element)
        _fixNamespaces(n as Element);
    }
  }
}

Index: modules/damieng/graphical_editor/daxe/web/daxe.dart
+++ modules/damieng/graphical_editor/daxe/web/daxe.dart
/*
  This file is part of Daxe.

  Daxe 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 3 of the License, or
  (at your option) any later version.

  Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

/**
 * Daxe - Dart XML Editor
 * The goal of this project is to replace the Jaxe Java applet in WebJaxe, but Daxe
 * could be used to edit XML documents online in any other application.
 * 
 * The URL must have the file and config parameters with the path to the XML file and Jaxe config file.
 * It can also have a save parameter with the path to a server script to save the document.
 */
import 'package:daxe/daxe.dart' as d;


void main() {
  // custom display types and functions could be added here in another application
  d.main();
}

Index: modules/damieng/graphical_editor/daxe/web/daxe.html
+++ modules/damieng/graphical_editor/daxe/web/daxe.html
<!DOCTYPE html>

<html>
  <head>
    <meta charset="utf-8">
    <title>Daxe</title>
    <link rel="stylesheet" href="packages/daxe/daxe.css">
  </head>
  <body>
    <textarea id="tacursor"></textarea><span id="caret"></span>
    <div id="headers"></div>
    <div id="doc1"><div id="doc2"></div></div>
    <div id="path"></div>

    <script type="application/dart" src="daxe.dart"></script>
    <script src="packages/browser/dart.js"></script>
    <!--<script src="packages/js/dart_interop.js"></script>--> <!-- for a packaged app -->
  </body>
</html>

Index: modules/damieng/graphical_editor/daxe/web/config/XHTML_config.xml
+++ modules/damieng/graphical_editor/daxe/web/config/XHTML_config.xml
<?xml version="1.0" encoding="UTF-8"?><CONFIG_JAXE>
<LANGAGE>
<FICHIER_SCHEMA nom="xhtml1-strict.xsd"/>
<RACINE element="html"/>
</LANGAGE>

<MENUS>
<MENU nom="structure">
<MENU_INSERTION nom="html" type_noeud="element"/>
<MENU_INSERTION nom="head" type_noeud="element"/>
<MENU_INSERTION nom="title" type_noeud="element"/>
<MENU_INSERTION nom="meta" type_noeud="element"/>
<MENU_INSERTION nom="link" type_noeud="element"/>
<MENU_INSERTION nom="body" type_noeud="element"/>
</MENU>
<MENU nom="divisions">
<MENU_INSERTION nom="h1" type_noeud="element"/>
<MENU_INSERTION nom="h2" type_noeud="element"/>
<MENU_INSERTION nom="h3" type_noeud="element"/>
<MENU_INSERTION nom="h4" type_noeud="element"/>
<MENU_INSERTION nom="h5" type_noeud="element"/>
<MENU_INSERTION nom="div" type_noeud="element"/>
<MENU_INSERTION nom="hr" type_noeud="element"/>
</MENU>
<MENU nom="texte">
<MENU_INSERTION nom="p" raccourci="P" type_noeud="element"/>
<MENU_INSERTION nom="a" type_noeud="element"/>
<MENU_INSERTION nom="blockquote" type_noeud="element"/>
<MENU_INSERTION nom="pre" type_noeud="element"/>
<MENU_INSERTION nom="em" type_noeud="element"/>
<MENU_INSERTION nom="span" type_noeud="element"/>
<MENU_INSERTION nom="strong" type_noeud="element"/>
<MENU_INSERTION nom="tt" type_noeud="element"/>
<MENU_INSERTION nom="abbr" type_noeud="element"/>
<MENU_INSERTION nom="acronym" type_noeud="element"/>
<MENU_INSERTION nom="address" type_noeud="element"/>
<MENU_INSERTION nom="br" type_noeud="element"/>
<MENU_INSERTION nom="cite" type_noeud="element"/>
<MENU_INSERTION nom="code" type_noeud="element"/>
<MENU_INSERTION nom="style" type_noeud="element"/>
<MENU_INSERTION nom="commentaire" type_noeud="commentaire"/>
</MENU>
<MENU nom="listes_tables">
<MENU_INSERTION nom="ul" type_noeud="element"/>
<MENU_INSERTION nom="li" type_noeud="element"/>
<MENU_INSERTION nom="ol" type_noeud="element"/>
<MENU_INSERTION nom="dl" type_noeud="element"/>
<MENU_INSERTION nom="dt" type_noeud="element"/>
<MENU_INSERTION nom="dd" type_noeud="element"/>
<MENU_INSERTION nom="table" type_noeud="element"/>
</MENU>
<MENU nom="objets">
<MENU_INSERTION nom="img" type_noeud="element"/>
<MENU_INSERTION nom="object" type_noeud="element"/>
<MENU_INSERTION nom="param" type_noeud="element"/>
</MENU>
<MENU nom="formulaires">
<MENU_INSERTION nom="form" type_noeud="element"/>
<MENU_INSERTION nom="input" type_noeud="element"/>
<MENU_INSERTION nom="label" type_noeud="element"/>
<MENU_INSERTION nom="select" type_noeud="element"/>
<MENU_INSERTION nom="option" type_noeud="element"/>
<MENU_INSERTION nom="textarea" type_noeud="element"/>
</MENU>
<MENU nom="styles">
<MENU_FONCTION classe="jaxe.FonctionNormal" nom="normal"/>
<MENU_INSERTION nom="sup" type_noeud="element"/>
<MENU_INSERTION nom="sub" type_noeud="element"/>
<MENU_INSERTION nom="i" type_noeud="element"/>
<MENU_INSERTION nom="b" type_noeud="element"/>
</MENU>
</MENUS>

<AFFICHAGE_NOEUDS>
<AFFICHAGE_ELEMENT element="html" type="division"/>
<AFFICHAGE_ELEMENT element="head" type="division"/>
<AFFICHAGE_ELEMENT element="title" type="string">
<PARAMETRE nom="style" valeur="GRAS"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="meta" type="vide">
<PARAMETRE nom="titreAtt" valeur="name"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="link" type="vide"/>
<AFFICHAGE_ELEMENT element="body" type="division"/>
<AFFICHAGE_ELEMENT element="h1" type="string">
<PARAMETRE nom="style" valeur="GRAS"/>
<PARAMETRE nom="police" valeur="Helvetica"/>
<PARAMETRE nom="taille" valeur="24"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="h2" type="string">
<PARAMETRE nom="style" valeur="GRAS"/>
<PARAMETRE nom="taille" valeur="16"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="h3" type="string">
<PARAMETRE nom="style" valeur="GRAS"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="h4" type="string"/>
<AFFICHAGE_ELEMENT element="h5" type="string"/>
<AFFICHAGE_ELEMENT element="div" type="division"/>
<AFFICHAGE_ELEMENT element="hr" type="vide"/>
<AFFICHAGE_ELEMENT element="p" type="zone"/>
<AFFICHAGE_ELEMENT element="a" type="string">
<PARAMETRE nom="titreAtt" valeur="name"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="blockquote" type="zone"/>
<AFFICHAGE_ELEMENT element="pre" type="zone"/>
<AFFICHAGE_ELEMENT element="em" type="string">
<PARAMETRE nom="style" valeur="ITALIQUE"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="span" type="string"/>
<AFFICHAGE_ELEMENT element="strong" type="string"/>
<AFFICHAGE_ELEMENT element="tt" type="string">
<PARAMETRE nom="police" valeur="Monospaced"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="abbr" type="string"/>
<AFFICHAGE_ELEMENT element="acronym" type="string"/>
<AFFICHAGE_ELEMENT element="address" type="string"/>
<AFFICHAGE_ELEMENT element="br" type="vide"/>
<AFFICHAGE_ELEMENT element="cite" type="string"/>
<AFFICHAGE_ELEMENT element="code" type="zone"/>
<AFFICHAGE_ELEMENT element="style" type="zone">
<PARAMETRE nom="titreAtt" valeur="type"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="ul" type="liste">
<PARAMETRE nom="typeListe" valeur="POINTS"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="li" type="item"/>
<AFFICHAGE_ELEMENT element="ol" type="liste">
<PARAMETRE nom="typeListe" valeur="NUMEROS"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="dl" type="zone"/>
<AFFICHAGE_ELEMENT element="dt" type="string"/>
<AFFICHAGE_ELEMENT element="dd" type="string"/>
<AFFICHAGE_ELEMENT element="table" type="tabletexte">
<PARAMETRE nom="trTag" valeur="tr"/>
<PARAMETRE nom="tdTag" valeur="td"/>
<PARAMETRE nom="thTag" valeur="th"/>
<PARAMETRE nom="colspanAttr" valeur="colspan"/>
<PARAMETRE nom="rowspanAttr" valeur="rowspan"/>
<PARAMETRE nom="alignAttr" valeur="align"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="tr" type="zone"/>
<AFFICHAGE_ELEMENT element="td" type="string"/>
<AFFICHAGE_ELEMENT element="th" type="string">
<PARAMETRE nom="style" valeur="GRAS"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="img" type="fichier">
<PARAMETRE nom="srcAtt" valeur="src"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="object" type="zone"/>
<AFFICHAGE_ELEMENT element="param" type="vide">
<PARAMETRE nom="titreAtt" valeur="name"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="form" type="zone"/>
<AFFICHAGE_ELEMENT element="input" type="vide"/>
<AFFICHAGE_ELEMENT element="label" type="string"/>
<AFFICHAGE_ELEMENT element="select" type="zone"/>
<AFFICHAGE_ELEMENT element="option" type="string"/>
<AFFICHAGE_ELEMENT element="textarea" type="string"/>
<AFFICHAGE_ELEMENT element="sup" type="style">
<PARAMETRE nom="style" valeur="EXPOSANT"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="sub" type="style">
<PARAMETRE nom="style" valeur="INDICE"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="i" type="style">
<PARAMETRE nom="style" valeur="ITALIQUE"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="b" type="style">
<PARAMETRE nom="style" valeur="GRAS"/>
</AFFICHAGE_ELEMENT>
</AFFICHAGE_NOEUDS>

<EXPORTS>
<EXPORT nom="HTML" sortie="HTML">
<FICHIER_XSL nom="XHTML.xsl"/>
</EXPORT>
</EXPORTS>

<STRINGS langue="en">
<DESCRIPTION_CONFIG>XHTML strict</DESCRIPTION_CONFIG>

<STRINGS_MENU menu="structure">
<TITRE>Structure</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="html">
<TITRE>html</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="head">
<TITRE>head</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="title">
<TITRE>title</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="meta">
<TITRE>meta</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="link">
<TITRE>link</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="body">
<TITRE>body</TITRE>
</STRINGS_MENU>

<STRINGS_MENU menu="divisions">
<TITRE>Divisions</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="h1">
<TITRE>heading 1</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="h2">
<TITRE>heading 2</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="h3">
<TITRE>heading 3</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="h4">
<TITRE>heading 4</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="h5">
<TITRE>heading 5</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="div">
<TITRE>division</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="hr">
<TITRE>horizontal line</TITRE>
</STRINGS_MENU>

<STRINGS_MENU menu="texte">
<TITRE>Text</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="p">
<TITRE>paragraph</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="a">
<TITRE>anchor</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="blockquote">
<TITRE>blockquote</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="pre">
<TITRE>preformat</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="em">
<TITRE>emphasis</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="strong">
<TITRE>strong</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="tt">
<TITRE>fixed width (tt)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="abbr">
<TITRE>abbreviation</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="acronym">
<TITRE>acronym</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="address">
<TITRE>address</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="br">
<TITRE>line break (br)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="cite">
<TITRE>citation</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="code">
<TITRE>code</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="style">
<TITRE>style</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="commentaire">
<TITRE>XML comment</TITRE>
</STRINGS_MENU>

<STRINGS_MENU menu="listes_tables">
<TITRE>Lists and Tables</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="ul">
<TITRE>bulleted list (ul)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="li">
<TITRE>list item (li)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="ol">
<TITRE>ordered list (ol)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="dl">
<TITRE>definition list (dl)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="dt">
<TITRE>definition term (dt)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="dd">
<TITRE>definition (dd)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="table">
<TITRE>table</TITRE>
</STRINGS_MENU>

<STRINGS_MENU menu="objets">
<TITRE>Objects</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="img">
<TITRE>image</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="object">
<TITRE>object</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="param">
<TITRE>parameter</TITRE>
</STRINGS_MENU>

<STRINGS_MENU menu="formulaires">
<TITRE>Forms</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="form">
<TITRE>form</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="input">
<TITRE>input</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="label">
<TITRE>label</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="select">
<TITRE>select</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="option">
<TITRE>option</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="textarea">
<TITRE>textarea</TITRE>
</STRINGS_MENU>

<STRINGS_MENU menu="styles">
<TITRE>Styles</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="normal">
<TITRE>standard</TITRE>
<DOCUMENTATION>
Removes all the styles in the selected text
</DOCUMENTATION>
</STRINGS_MENU>
<STRINGS_MENU menu="sup">
<TITRE>superscript</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="sub">
<TITRE>subscript</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="i">
<TITRE>italic</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="b">
<TITRE>bold</TITRE>
</STRINGS_MENU>
</STRINGS>


<STRINGS langue="fr">
<DESCRIPTION_CONFIG>XHTML strict</DESCRIPTION_CONFIG>

<STRINGS_MENU menu="structure">
<TITRE>Structure</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="html">
<TITRE>Document XHTML</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="head">
<TITRE>Entête (head)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="title">
<TITRE>Titre (title)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="meta">
<TITRE>Métadonnée (meta)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="link">
<TITRE>Lien (link)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="body">
<TITRE>Corps du document (body)</TITRE>
</STRINGS_MENU>

<STRINGS_MENU menu="divisions">
<TITRE>Divisions</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="h1">
<TITRE>Titre niveau 1 (h1)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="h2">
<TITRE>Titre niveau 2 (h2)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="h3">
<TITRE>Titre niveau 3 (h3)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="h4">
<TITRE>Titre niveau 4 (h4)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="h5">
<TITRE>Titre niveau 5 (h5)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="div">
<TITRE>Division (div)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="hr">
<TITRE>ligne de division (hr)</TITRE>
</STRINGS_MENU>

<STRINGS_MENU menu="texte">
<TITRE>Texte</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="p">
<TITRE>Paragraphe (p)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="a">
<TITRE>Ancre (a)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="blockquote">
<TITRE>Indentation (blockquote)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="pre">
<TITRE>Texte préformaté (pre)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="em">
<TITRE>Emphase (em)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="strong">
<TITRE>Appuyé (strong)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="tt">
<TITRE>Télétype (tt)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="abbr">
<TITRE>Abbréviation (abbr)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="acronym">
<TITRE>Acronyme (acronym)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="address">
<TITRE>Adresse (address)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="br">
<TITRE>Saut de ligne (br)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="cite">
<TITRE>Citation (cite)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="code">
<TITRE>Code (code)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="style">
<TITRE>Style (style)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="commentaire">
<TITRE>Commentaire XML</TITRE>
</STRINGS_MENU>

<STRINGS_MENU menu="listes_tables">
<TITRE>Listes/Tables</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="ul">
<TITRE>Liste à points (ul)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="li">
<TITRE>élément de liste (li)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="ol">
<TITRE>Liste numérotée (ol)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="dl">
<TITRE>Liste de définitions (dl)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="dt">
<TITRE>Titre de liste de déf. (dt)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="dd">
<TITRE>Définition de liste de déf. (dd)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="table">
<TITRE>Table (table)</TITRE>
</STRINGS_MENU>

<STRINGS_MENU menu="objets">
<TITRE>Objets</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="img">
<TITRE>Image (img)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="object">
<TITRE>Objet (object)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="param">
<TITRE>Paramètre (param)</TITRE>
</STRINGS_MENU>

<STRINGS_MENU menu="formulaires">
<TITRE>Formulaires</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="form">
<TITRE>Formulaire (form)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="input">
<TITRE>Entrée de donnée (input)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="label">
<TITRE>Label (label)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="select">
<TITRE>Menu de sélection (select)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="option">
<TITRE>Option de sélection (option)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="textarea">
<TITRE>Zone de texte (textarea)</TITRE>
</STRINGS_MENU>

<STRINGS_MENU menu="styles">
<TITRE>Styles</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="normal">
<TITRE>Sans style</TITRE>
<DOCUMENTATION>
Retire tous les styles du texte sélectionné, le transformant en texte normal
</DOCUMENTATION>
</STRINGS_MENU>
<STRINGS_MENU menu="sup">
<TITRE>Exposant (sup)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="sub">
<TITRE>Indice (sub)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="i">
<TITRE>Italique (i)</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="b">
<TITRE>Gras (b)</TITRE>
</STRINGS_MENU>
</STRINGS>

</CONFIG_JAXE>

Index: modules/damieng/graphical_editor/daxe/web/config/XPAGES.xsd
+++ modules/damieng/graphical_editor/daxe/web/config/XPAGES.xsd
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xml:lang="fr" xmlns:xs="http://www.w3.org/2001/XMLSchema">


<!-- Types de données -->

<xs:simpleType name="typeLabel">
    <xs:restriction base="xs:string">
        <xs:pattern value="[\-a-z0-9]*"/>
    </xs:restriction>
</xs:simpleType>

<xs:simpleType name="typeDate">
    <xs:restriction base="xs:string">
        <xs:pattern value="[0-3]?[0-9]/[0-1]?[0-9]/[1-2][0-9][0-9][0-9]"/>
    </xs:restriction>
</xs:simpleType>

<xs:simpleType name="typeURL">
    <xs:restriction base="xs:string">
        <xs:pattern value="http://.*"/>
    </xs:restriction>
</xs:simpleType>


<!-- Elément racine -->

<xs:element name="XPAGES">
    <xs:annotation>
        <xs:documentation>
        Un ensemble de pages qui se suivent, sur un sujet donné.
        </xs:documentation>
    </xs:annotation>
    <xs:complexType>
        <xs:sequence>
            <xs:element ref="INFORMATIONS"/>
            <xs:choice maxOccurs="unbounded">
                <xs:element ref="PAGE"/>
                <xs:element ref="REFXPAGES"/>
                <xs:element ref="XPAGES"/>
            </xs:choice>
        </xs:sequence>
    </xs:complexType>
</xs:element>


<!-- Métadonnées -->

<xs:element name="INFORMATIONS">
    <xs:annotation>
        <xs:documentation>
        Informations sur le document. Le label et le titre sont obligatoires.
        </xs:documentation>
    </xs:annotation>
    <xs:complexType>
        <xs:sequence>
            <xs:element ref="LABEL"/>
            <xs:element ref="TITRE"/>
            <xs:element minOccurs="0" maxOccurs="unbounded" ref="AUTEUR"/>
            <xs:element minOccurs="0" ref="DATECRE"/>
            <xs:element minOccurs="0" ref="DATEMAJ"/>
            <xs:element minOccurs="0" ref="LOGO"/>
            <xs:element minOccurs="0" ref="INTERFACE"/>
            <xs:element minOccurs="0" ref="RUBRIQUES"/>
            <xs:element minOccurs="0" ref="LANGUE"/>
        </xs:sequence>
    </xs:complexType>
</xs:element>

<xs:element name="LABEL" type="typeLabel">
    <xs:annotation>
        <xs:documentation>
        Identifiant pour cette contribution.
        Ne peut contenir que des lettres de a à z, des chiffres de 0 à 9, et le caractère "-".
        Le nom du fichier XML et le nom du dossier doivent correspondre au label.
        </xs:documentation>
    </xs:annotation>
</xs:element>

<xs:element name="TITRE" type="xs:string">
    <xs:annotation>
        <xs:documentation>
        Titre de l'ensemble de pages.
        </xs:documentation>
    </xs:annotation>
</xs:element>

<xs:element name="AUTEUR" type="xs:string"/>

<xs:element name="DATECRE" type="typeDate">
    <xs:annotation>
        <xs:documentation>
            Date de création, au format JJ/MM/AAAA
        </xs:documentation>
    </xs:annotation>
</xs:element>

<xs:element name="DATEMAJ" type="typeDate">
    <xs:annotation>
        <xs:documentation>
            Date de mise à jour, au format JJ/MM/AAAA
        </xs:documentation>
    </xs:annotation>
</xs:element>

<xs:element name="LOGO">
    <xs:annotation>
        <xs:documentation>
        Fichier image d'un logo à utiliser pour l'interface du site.
        </xs:documentation>
    </xs:annotation>
    <xs:complexType>
        <xs:attribute name="fichier" type="xs:string" use="required">
            <xs:annotation>
                <xs:documentation>
                Chemin vers le fichier de l'image, relatif au fichier XML.
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>
        <xs:attribute name="lien" type="xs:string" use="optional">
            <xs:annotation>
                <xs:documentation>
                Lien optionnel d'une page web vers laquelle aller quand on clique sur le logo.
                Doit commencer par "http://".
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>
    </xs:complexType>
</xs:element>

<xs:element name="INTERFACE" type="xs:string">
    <xs:annotation>
        <xs:documentation>
        Interface des pages.
        </xs:documentation>
    </xs:annotation>
</xs:element>

<xs:element name="RUBRIQUES">
    <xs:annotation>
        <xs:documentation>
        Afficher les rubriques ou non. Par défaut, utilise la valeur de l'élément parent.
        L'élément XPAGES de plus haut niveau a la valeur "oui" par défaut.
        </xs:documentation>
    </xs:annotation>
    <xs:simpleType>
        <xs:restriction base="xs:string">
            <xs:enumeration value="oui"/>
            <xs:enumeration value="non"/>
        </xs:restriction>
    </xs:simpleType>
</xs:element>

<xs:element name="LANGUE">
    <xs:annotation>
        <xs:documentation>
        Code de la langue à utiliser pour l'interface utilisateur.
        </xs:documentation>
    </xs:annotation>
    <xs:simpleType>
        <xs:restriction base="xs:string">
            <xs:enumeration value="fr"/>
            <xs:enumeration value="en"/>
        </xs:restriction>
    </xs:simpleType>
</xs:element>


<!-- Pages -->

<xs:element name="REFXPAGES">
    <xs:annotation>
        <xs:documentation>
        Référence vers un autre ensemble de pages, dont le titre sera affiché
        dans le sommaire de cet ensemble.
        </xs:documentation>
    </xs:annotation>
    <xs:complexType>
        <xs:attribute name="label" type="typeLabel" use="required">
            <xs:annotation>
                <xs:documentation>
                    Label du document XPAGES référencé.
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>
    </xs:complexType>
</xs:element>

<xs:element name="PAGE">
    <xs:annotation>
        <xs:documentation>
        Page écran, d'environ 1000 caractères.
        </xs:documentation>
    </xs:annotation>
    <xs:complexType>
        <xs:choice maxOccurs="unbounded">
            <xs:element ref="SECTION"/>
            <xs:element ref="PARAGRAPHE"/>
            <xs:element ref="LISTE"/>
            <xs:element ref="TABLEAU"/>
            <xs:element ref="ENVIMAGE"/>
            <xs:element ref="APPLICATION"/>
            <xs:element ref="COMMENTAIRE"/>
            <xs:element ref="CODE"/>
            <xs:element ref="EXERCICE"/>
            <xs:element ref="QCM"/>
            <xs:element ref="BIBLIOGRAPHIE"/>
        </xs:choice>
        <xs:attribute name="label" type="typeLabel" use="required">
            <xs:annotation>
                <xs:documentation>
                    Label de la page (identifiant caché permettant de faire un lien vers cette page).
                    Ne doit être composé que de lettres minuscules de a à z.
                    Utiliser le label "index" pour la page d'entrée du site.
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>
        <xs:attribute name="titre" type="xs:string" use="required">
            <xs:annotation>
                <xs:documentation>
                    Titre de la page.
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>
        <xs:attribute name="role" type="xs:string" use="optional" default="autre">
            <xs:annotation>
                <xs:documentation>
                    Rôle sémantique de la page : sens qu'on lui donne.
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>
    </xs:complexType>
</xs:element>


<!-- Eléments de bloc -->

<xs:element name="SECTION">
    <xs:annotation>
        <xs:documentation>
        Une section à l'intérieur d'une page, qui peut avoir un titre.
        Le rôle permet de donner un sens à la section, pour des transformations XML ultérieures ou un affichage particulier.
        L'importance "grande" permet de mettre en valeur la section par rapport au reste du texte.
        </xs:documentation>
    </xs:annotation>
    <xs:complexType>
        <xs:choice maxOccurs="unbounded">
            <xs:element ref="SECTION"/>
            <xs:element ref="PARAGRAPHE"/>
            <xs:element ref="LISTE"/>
            <xs:element ref="TABLEAU"/>
            <xs:element ref="ENVIMAGE"/>
            <xs:element ref="APPLICATION"/>
            <xs:element ref="COMMENTAIRE"/>
            <xs:element ref="CODE"/>
            <xs:element ref="EXERCICE"/>
            <xs:element ref="QCM"/>
        </xs:choice>
        <xs:attribute name="titre" type="xs:string" use="optional">
            <xs:annotation>
                <xs:documentation>
                    Titre de la section.
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>
        <xs:attribute name="role" type="xs:string" use="optional" default="autre">
            <xs:annotation>
                <xs:documentation>
                    Rôle sémantique de la section : sens qu'on lui donne.
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>
        <xs:attribute name="importance" use="optional" default="normale">
            <xs:annotation>
                <xs:documentation>
                    Importance de la section. Une section importante pourra être mise en évidence
                    dans le site web généré.
                </xs:documentation>
            </xs:annotation>
            <xs:simpleType>
                <xs:restriction base="xs:string">
                    <xs:enumeration value="normale"/>
                    <xs:enumeration value="grande"/>
                </xs:restriction>
            </xs:simpleType>
        </xs:attribute>
    </xs:complexType>
</xs:element>

<xs:element name="PARAGRAPHE">
    <xs:complexType mixed="true">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
            <xs:group ref="texte"/>
        </xs:choice>
    </xs:complexType>
</xs:element>

<xs:element name="ENVIMAGE">
    <xs:annotation>
        <xs:documentation>
        Environnement pour des images ou animations.
        </xs:documentation>
    </xs:annotation>
    <xs:complexType>
        <xs:sequence>
            <xs:choice maxOccurs="unbounded">
                <xs:element ref="FICHIER"/>
                <xs:element ref="FORMATS"/>
            </xs:choice>
            <xs:element minOccurs="0" ref="LEGENDE"/>
            <xs:element minOccurs="0" ref="CREDIT"/>
        </xs:sequence>
        <xs:attribute name="titre" type="xs:string" use="optional">
            <xs:annotation>
                <xs:documentation>
                    Titre de l'image ou animation.
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>
        <xs:attribute name="localisation" use="required">
            <xs:annotation>
                <xs:documentation>
                    Positionnement souhaité pour l'image :
                    - texte : dans le texte à la taille d'origine
                    - page : dans la page à côté du texte, réduit si nécessaire
                    - icône : sous forme de petite icône à côté du paragraphe qui suit
                </xs:documentation>
            </xs:annotation>
            <xs:simpleType>
                <xs:restriction base="xs:string">
                    <xs:enumeration value="texte"/>
                    <xs:enumeration value="page"/>
                    <xs:enumeration value="icône"/>
                </xs:restriction>
            </xs:simpleType>
        </xs:attribute>
        <xs:attribute name="label" type="typeLabel" use="optional">
            <xs:annotation>
                <xs:documentation>
                    Label permettant de faire un lien vers l'image.
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>
    </xs:complexType>
</xs:element>

<xs:element name="LEGENDE">
    <xs:annotation>
        <xs:documentation>
        Légende
        </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
            <xs:group ref="texte"/>
        </xs:choice>
    </xs:complexType>
</xs:element>

<xs:element name="FICHIER">
    <xs:annotation>
        <xs:documentation>
        Fichier image ou animation à inclure dans le texte,
        ou à localiser dans une page qui s'ouvrira à part du texte.
        Si le contenu du fichier doit être accompagné d'une légende,
        il est nécessaire de passer par l'élément ENVIMAGE.
        </xs:documentation>
    </xs:annotation>
    <xs:complexType>
        <xs:attribute name="nom" type="xs:string" use="required"/>
    </xs:complexType>
</xs:element>

<xs:element name="FORMATS">
    <xs:annotation>
        <xs:documentation>
        Permet de spécifier plusieurs fichiers, avec des formats différents, pour un même media.
        Un seul fichier sera affiché, choisi en fonction des capacités de l'environnement d'affichage.
        Cet élément n'est utile qu'avec des formats qui ne sont pas lisibles partout, par exemple pour des vidéos.
        </xs:documentation>
    </xs:annotation>
    <xs:complexType>
        <xs:sequence>
            <xs:element ref="FICHIER" maxOccurs="unbounded"/>
        </xs:sequence>
    </xs:complexType>
</xs:element>

<xs:element name="CREDIT">
    <xs:annotation>
        <xs:documentation>
        crédit des documents
        </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
            <xs:element ref="LIENPAGE"/>
            <xs:element ref="LIENWEB"/>
            <xs:group ref="styletexte"/>
        </xs:choice>
    </xs:complexType>
</xs:element>

<xs:element name="TABLEAU">
    <xs:annotation>
        <xs:documentation>
        Tableau.
        </xs:documentation>
    </xs:annotation>
    <xs:complexType>
        <xs:choice maxOccurs="unbounded" minOccurs="0">
            <xs:element ref="TR"/>
        </xs:choice>
        <xs:attribute name="titre" type="xs:string" use="optional">
            <xs:annotation>
                <xs:documentation>
                    Titre du tableau
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>
    </xs:complexType>
</xs:element>

<xs:element name="TR">
    <xs:annotation>
        <xs:documentation>
        Ligne d'un tableau
        </xs:documentation>
    </xs:annotation>
    <xs:complexType>
        <xs:choice maxOccurs="unbounded" minOccurs="0">
            <xs:element ref="TD"/>
            <xs:element ref="TH"/>
        </xs:choice>
    </xs:complexType>
</xs:element>

<xs:element name="TD">
    <xs:annotation>
        <xs:documentation>
        Cellule d'un tableau
        </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
            <xs:group ref="texte"/>
        </xs:choice>
        <xs:attribute name="align" use="optional">
            <xs:annotation>
                <xs:documentation>
                    Alignement du texte de la cellule
                </xs:documentation>
            </xs:annotation>
            <xs:simpleType>
                <xs:restriction base="xs:string">
                    <xs:enumeration value="left"/>
                    <xs:enumeration value="center"/>
                    <xs:enumeration value="right"/>
                    <xs:enumeration value="justify"/>
                </xs:restriction>
            </xs:simpleType>
        </xs:attribute>
        <xs:attribute name="colspan" type="xs:positiveInteger" use="optional">
            <xs:annotation>
                <xs:documentation>
                    Regroupement horizontal de cellules :
                    nombres de cellules à regrouper à droite de celle-ci
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>
        <xs:attribute name="rowspan" type="xs:positiveInteger" use="optional">
            <xs:annotation>
                <xs:documentation>
                    Regroupement vertical de cellules :
                    nombres de cellules à regrouper en bas de celle-ci
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>
    </xs:complexType>
</xs:element>

<xs:element name="TH">
    <xs:annotation>
        <xs:documentation>
        Entête d'une colonne
        </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
            <xs:group ref="texte"/>
        </xs:choice>
        <xs:attribute name="align" use="optional">
            <xs:annotation>
                <xs:documentation>
                    Alignement du texte de la cellule
                </xs:documentation>
            </xs:annotation>
            <xs:simpleType>
                <xs:restriction base="xs:string">
                    <xs:enumeration value="left"/>
                    <xs:enumeration value="center"/>
                    <xs:enumeration value="right"/>
                    <xs:enumeration value="justify"/>
                </xs:restriction>
            </xs:simpleType>
        </xs:attribute>
        <xs:attribute name="colspan" type="xs:positiveInteger" use="optional">
            <xs:annotation>
                <xs:documentation>
                    Regroupement horizontal de cellules :
                    nombres de cellules à regrouper à droite de celle-ci
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>
        <xs:attribute name="rowspan" type="xs:positiveInteger" use="optional">
            <xs:annotation>
                <xs:documentation>
                    Regroupement vertical de cellules :
                    nombres de cellules à regrouper en bas de celle-ci
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>
    </xs:complexType>
</xs:element>

<xs:element name="LISTE">
    <xs:annotation>
        <xs:documentation>
        Liste avec des puces ou des numéros
        </xs:documentation>
    </xs:annotation>
    <xs:complexType>
        <xs:sequence maxOccurs="unbounded" minOccurs="1">
            <xs:element ref="ITEM"/>
        </xs:sequence>
        <xs:attribute name="type" use="optional" default="puces">
            <xs:simpleType>
                <xs:restriction base="xs:string">
                    <xs:enumeration value="puces"/>
                    <xs:enumeration value="numéros"/>
                </xs:restriction>
            </xs:simpleType>
        </xs:attribute>
    </xs:complexType>
</xs:element>

<xs:element name="ITEM">
    <xs:annotation>
        <xs:documentation>
        Elément d'une liste
        </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
            <xs:element ref="PARAGRAPHE"/>
            <xs:element ref="LISTE"/>
            <xs:group ref="texte"/>
        </xs:choice>
    </xs:complexType>
</xs:element>

<xs:element name="APPLICATION">
    <xs:annotation>
        <xs:documentation>
        Application interactive. Remplace les éléments APPLET et FLASH en généralisant le concept.
        L'attribut type précise le type d'application (applet Java, Flash ou HTML/Javascript).
        Le fichier indiqué doit être un .jar pour les applets Java, un .swf pour les applications Flash,
        et un .html pour les applications Javascript.
        Les applets Java doivent avoir un paramètre "code" avec le nom de la classe.
        </xs:documentation>
    </xs:annotation>
    <xs:complexType>
        <xs:sequence>
            <xs:element ref="PARAM" minOccurs="0" maxOccurs="unbounded"/>
            <xs:element ref="LEGENDE" minOccurs="0"/>
            <xs:element ref="CREDIT" minOccurs="0"/>
        </xs:sequence>
        <xs:attribute name="type" use="required">
            <xs:annotation>
                <xs:documentation>
                    Type d'application (applet Java, Flash ou HTML/Javascript).
                </xs:documentation>
            </xs:annotation>
            <xs:simpleType>
                <xs:restriction base="xs:string">
                    <xs:enumeration value="applet"/>
                    <xs:enumeration value="flash"/>
                    <xs:enumeration value="html"/>
                </xs:restriction>
            </xs:simpleType>
        </xs:attribute>
        <xs:attribute name="titre" type="xs:string" use="optional">
            <xs:annotation>
                <xs:documentation>
                    Titre de l'application.
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>
        <xs:attribute name="fichier" use="required">
            <xs:annotation>
                <xs:documentation>
                    Fichier de l'application.
                    Doit être un .jar pour les applets Java, un .swf pour les applications Flash,
                    et un .html pour les applications Javascript.
                </xs:documentation>
            </xs:annotation>
            <xs:simpleType>
                <xs:restriction base="xs:string">
                    <xs:pattern value=".+\.(jar|swf|html)"/>
                </xs:restriction>
            </xs:simpleType>
        </xs:attribute>
        <xs:attribute name="contribution" type="typeLabel" use="optional">
            <xs:annotation>
                <xs:documentation>
                    Contribution du fichier de l'application.
                    Permet de ne pas avoir à recopier plusieurs fois une application utilisée dans plusieurs contributions.
                    Les applications doivent lire les fichiers dont les noms sont passés en paramètres à partir
                    du chemin du fichier HTML et non du chemin du fichier de l'application.
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>
        <xs:attribute name="largeur" type="xs:int" use="required">
            <xs:annotation>
                <xs:documentation>
                    Largeur de la zone de l'application, en pixels.
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>
        <xs:attribute name="hauteur" type="xs:int" use="required">
            <xs:annotation>
                <xs:documentation>
                    Hauteur de la zone de l'application, en pixels.
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>
        <xs:attribute name="localisation" use="required">
            <xs:annotation>
                <xs:documentation>
                    Positionnement souhaité pour l'application :
                    - texte : dans le texte
                    - page : dans la page à côté du texte
                    - icône : accessible à partir d'une petite icône à côté du paragraphe qui suit
                </xs:documentation>
            </xs:annotation>
            <xs:simpleType>
                <xs:restriction base="xs:string">
                    <xs:enumeration value="texte"/>
                    <xs:enumeration value="page"/>
                    <xs:enumeration value="icône"/>
                </xs:restriction>
            </xs:simpleType>
        </xs:attribute>
    </xs:complexType>
</xs:element>

<xs:element name="PARAM">
    <xs:annotation>
        <xs:documentation>
        Paramètre d'application interactive
        </xs:documentation>
    </xs:annotation>
    <xs:complexType>
        <xs:attribute name="nom" type="xs:string" use="required">
            <xs:annotation>
                <xs:documentation>
                    Nom du paramètre.
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>
        <xs:attribute name="valeur" type="xs:string" use="required">
            <xs:annotation>
                <xs:documentation>
                    Valeur du paramètre.
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>
    </xs:complexType>
</xs:element>

<xs:element name="COMMENTAIRE" type="xs:string">
    <xs:annotation>
        <xs:documentation>
        Commentaire de l'auteur, qui ne sera pas affiché dans le résultat final.
        </xs:documentation>
    </xs:annotation>
</xs:element>


<!-- Exercices -->

<xs:element name="EXERCICE">
    <xs:annotation>
        <xs:documentation>
            Exercice de type questions-réponses
        </xs:documentation>
    </xs:annotation>
    <xs:complexType>
        <xs:sequence>
            <xs:element minOccurs="0" maxOccurs="unbounded" ref="AUTEUR"/>
            <xs:element ref="ENONCE" minOccurs="0"/>
            <xs:element ref="DIFFICULTE" minOccurs="0"/>
            <xs:element ref="TEMPS" minOccurs="0"/>
            <xs:element ref="QUESTION" maxOccurs="unbounded"/>
        </xs:sequence>
        <xs:attribute name="titre" type="xs:string" use="optional"/>
        <xs:attribute name="type" use="optional" default="évaluation">
            <xs:annotation>
                <xs:documentation>
                    Type d'exercice. Les solutions des exercices de type "évaluation" ne sont
                    affichées qu'avec la sortie "solutions", alors que les solutions des exercices
                    de type "auto-évaluation" sont toujours affichées.
                </xs:documentation>
            </xs:annotation>
            <xs:simpleType>
                <xs:restriction base="xs:string">
                    <xs:enumeration value="évaluation"/>
                    <xs:enumeration value="auto-évaluation"/>
                </xs:restriction>
            </xs:simpleType>
        </xs:attribute>
    </xs:complexType>
</xs:element>

<xs:element name="ENONCE">
    <xs:annotation>
        <xs:documentation>
            Enoncé d'un exercice ou d'une question
        </xs:documentation>
    </xs:annotation>
    <xs:complexType>
        <xs:choice minOccurs="0" maxOccurs="unbounded">
            <xs:element ref="SECTION"/>
            <xs:element ref="PARAGRAPHE"/>
            <xs:element ref="LISTE"/>
            <xs:element ref="TABLEAU"/>
            <xs:element ref="ENVIMAGE"/>
            <xs:element ref="APPLICATION"/>
        </xs:choice>
    </xs:complexType>
</xs:element>

<xs:element name="DIFFICULTE">
    <xs:annotation>
        <xs:documentation>
            indication du niveau de difficulté :
            * : facile, ou application directe du cours
            ** : moyen
            *** : difficile
        </xs:documentation>
    </xs:annotation>
    <xs:simpleType>
        <xs:restriction base="xs:string">
            <xs:enumeration value="*"/>
            <xs:enumeration value="**"/>
            <xs:enumeration value="***"/>
        </xs:restriction>
    </xs:simpleType>
</xs:element>

<xs:element name="TEMPS" type="xs:string">
    <xs:annotation>
        <xs:documentation>
            temps indicatif nécessaire pour résoudre l'exercice
        </xs:documentation>
    </xs:annotation>
</xs:element>

<xs:element name="QUESTION">
    <xs:annotation>
        <xs:documentation>
            Question d'un exercice, avec un énoncé, éventuellement des aides, une solution,
            et éventuellement des points.
        </xs:documentation>
    </xs:annotation>
    <xs:complexType>
        <xs:sequence>
            <xs:element ref="ENONCE"/>
            <xs:element ref="AIDE" minOccurs="0" maxOccurs="unbounded"/>
            <xs:element ref="SOLUTION" minOccurs="0"/>
            <xs:element ref="POINTS" minOccurs="0"/>
        </xs:sequence>
    </xs:complexType>
</xs:element>

<xs:element name="SOLUTION">
    <xs:complexType>
        <xs:choice minOccurs="0" maxOccurs="unbounded">
            <xs:element ref="SECTION"/>
            <xs:element ref="PARAGRAPHE"/>
            <xs:element ref="LISTE"/>
            <xs:element ref="TABLEAU"/>
            <xs:element ref="ENVIMAGE"/>
            <xs:element ref="APPLICATION"/>
        </xs:choice>
    </xs:complexType>
</xs:element>

<xs:element name="POINTS" type="xs:float">
    <xs:annotation>
        <xs:documentation>
            Nombre de points gagnés si la réponse est correcte.
        </xs:documentation>
    </xs:annotation>
</xs:element>

<xs:element name="QCM">
    <xs:annotation>
        <xs:documentation>
            Questionnaire à choix multiples, avec des commentaires affichés
            après le choix de l'utilisateur.
        </xs:documentation>
    </xs:annotation>
    <xs:complexType>
        <xs:sequence>
            <xs:element minOccurs="0" maxOccurs="unbounded" ref="AUTEUR"/>
            <xs:element ref="ENONCE" minOccurs="0"/>
            <xs:element ref="DIFFICULTE" minOccurs="0"/>
            <xs:element ref="TEMPS" minOccurs="0"/>
            <xs:element ref="QUESTIONQCM" maxOccurs="unbounded"/>
        </xs:sequence>
        <xs:attribute name="titre" type="xs:string" use="optional"/>
    </xs:complexType>
</xs:element>

<xs:element name="AIDE">
    <xs:annotation>
        <xs:documentation>
            Aide d'un exercice.
        </xs:documentation>
    </xs:annotation>
    <xs:complexType>
        <xs:choice minOccurs="0" maxOccurs="unbounded">
            <xs:element ref="PARAGRAPHE"/>
            <xs:element ref="LISTE"/>
            <xs:element ref="TABLEAU"/>
            <xs:element ref="ENVIMAGE"/>
            <xs:element ref="APPLICATION"/>
        </xs:choice>
    </xs:complexType>
</xs:element>

<xs:element name="QUESTIONQCM">
    <xs:annotation>
        <xs:documentation>
            Question d'un QCM, avec un énoncé, éventuellement des aides,
            et les réponses possibles.
        </xs:documentation>
    </xs:annotation>
    <xs:complexType>
        <xs:sequence>
            <xs:element ref="ENONCEQCM"/>
            <xs:element ref="AIDE" minOccurs="0" maxOccurs="unbounded"/>
            <xs:element ref="REPONSEQCM" maxOccurs="unbounded"/>
        </xs:sequence>
    </xs:complexType>
</xs:element>

<xs:element name="ENONCEQCM">
    <xs:annotation>
        <xs:documentation>
            Enoncé d'un QCM (texte très court)
        </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
            <xs:group ref="texte"/>
        </xs:choice>
    </xs:complexType>
</xs:element>

<xs:element name="REPONSEQCM">
    <xs:annotation>
        <xs:documentation>
            Réponse possible à un QCM.
            Contient le texte de la réponse (TEXTEREP)
            et un commentaire (COMMENTAIREREP) à afficher si la réponse donnée est incorrecte.
            L'attribut bonne indique si la réponse est bonne ou pas.
        </xs:documentation>
    </xs:annotation>
    <xs:complexType>
        <xs:sequence>
            <xs:element ref="TEXTEREP"/>
            <xs:element ref="COMMENTAIREREP" minOccurs="0"/>
        </xs:sequence>
        <xs:attribute use="required" name="bonne">
            <xs:simpleType>
                <xs:restriction base="xs:string">
                    <xs:enumeration value="non"/>
                    <xs:enumeration value="oui"/>
                </xs:restriction>
            </xs:simpleType>
        </xs:attribute>
    </xs:complexType>
</xs:element>

<xs:element name="TEXTEREP">
    <xs:annotation>
        <xs:documentation>
            Texte de la réponse
        </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
        <xs:choice minOccurs="0" maxOccurs="unbounded">
            <xs:group ref="texte"/>
        </xs:choice>
    </xs:complexType>
</xs:element>

<xs:element name="COMMENTAIREREP" type="xs:string">
    <xs:annotation>
        <xs:documentation>
            Commentaire à afficher si la réponse donnée est incorrecte
        </xs:documentation>
    </xs:annotation>
</xs:element>


<!-- Eléments mélangés à du texte -->

<xs:group name="texte">
    <xs:annotation>
        <xs:documentation>
        Ensemble des éléments que l'on retrouve mélangés à du texte
        </xs:documentation>
    </xs:annotation>
    <xs:choice>
        <xs:element ref="LIENPAGE"/>
        <xs:element ref="LIENWEB"/>
        <xs:element ref="LIENIMAGE"/>
        <xs:element ref="FICHIER"/>
        <xs:element ref="SYMBOLE"/>
        <xs:element ref="EQUATION"/>
        <xs:element ref="EQUATEX"/>
        <xs:element ref="GLOSSAIRE"/>
        <xs:group ref="styletexte"/>
    </xs:choice>
</xs:group>

<xs:group name="styletexte">
    <xs:annotation>
        <xs:documentation>
        styles et espaces pour le texte
        </xs:documentation>
    </xs:annotation>
    <xs:choice>
        <xs:element ref="EM"/>
        <xs:element ref="SUP"/>
        <xs:element ref="SUB"/>
        <xs:element ref="CODE"/>
        <xs:element ref="NBSP"/>
    </xs:choice>
</xs:group>

<xs:element name="LIENPAGE">
    <xs:annotation>
        <xs:documentation>
        Lien vers une page de la même contribution (si contribution est vide),
        ou vers une page d'une autre contribution (avec contribution = le label de la contribution).
        Doit contenir du texte ou un fichier sur lequel sera placé le lien.
        </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
            <xs:element ref="FICHIER"/>
            <xs:element ref="EM"/>
            <xs:element ref="SUP"/>
            <xs:element ref="SUB"/>
            <xs:element ref="EQUATION"/>
            <xs:element ref="EQUATEX"/>
        </xs:choice>
        <xs:attribute name="contribution" type="typeLabel" use="optional">
            <xs:annotation>
                <xs:documentation>
                Le label de la contribution. Vide si on veut faire un lien vers la même contribution.
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>
        <xs:attribute name="page" type="typeLabel" use="optional">
            <xs:annotation>
                <xs:documentation>
                Le label de la page. Vide si on veut faire un lien vers la première page de la contribution.
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>
    </xs:complexType>
</xs:element>

<xs:element name="LIENWEB">
    <xs:annotation>
        <xs:documentation>
        Lien externe (avec url commençant par "http://").
        Doit contenir du texte ou un fichier sur lequel sera placé le lien.
        </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
            <xs:element ref="FICHIER"/>
            <xs:element ref="EM"/>
            <xs:element ref="SUP"/>
            <xs:element ref="SUB"/>
            <xs:element ref="EQUATION"/>
            <xs:element ref="EQUATEX"/>
        </xs:choice>
        <xs:attribute name="url" type="xs:string" use="required">
            <xs:annotation>
                <xs:documentation>
                Adresse de la page web. Doit commencer par "http://".
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>
    </xs:complexType>
</xs:element>

<xs:element name="LIENIMAGE">
    <xs:annotation>
        <xs:documentation>
        Lien vers un environnement d'image dans la même page.
        Doit contenir du texte ou un fichier sur lequel sera placé le lien.
        </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
            <xs:element ref="FICHIER"/>
            <xs:element ref="EM"/>
            <xs:element ref="SUP"/>
            <xs:element ref="SUB"/>
            <xs:element ref="EQUATION"/>
            <xs:element ref="EQUATEX"/>
        </xs:choice>
        <xs:attribute name="label" type="typeLabel" use="required">
            <xs:annotation>
                <xs:documentation>
                Label de l'environnement d'image (qui doit se trouver dans la même page).
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>
    </xs:complexType>
</xs:element>

<xs:element name="SYMBOLE">
    <xs:annotation>
        <xs:documentation>
            Caractère spécial ou petite image parmi les symboles de Jaxe.
            Quand l'attribut nom avec le chemin vers l'image n'est pas utilisé,
            le symbole doit se trouver à l'intérieur de l'élément sous forme d'une entité caractère.
        </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
        <xs:attribute name="nom" type="xs:string" use="optional"/>
    </xs:complexType>
</xs:element>

<xs:element name="EQUATION">
    <xs:annotation>
        <xs:documentation>
        Equation: texte de l'équation dans l'attribut texte (avec la syntaxe de Jaxe),
        et image PNG encodée en base64 à l'intérieur de l'élément.
        Plus tard l'image sera redirigée vers un fichier dont la référence
        sera dans un attribut "image".
        </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
        <xs:attribute name="texte" type="xs:string" use="required">
            <xs:annotation>
                <xs:documentation>
                Texte de l'équation, avec la syntaxe de Jaxe.
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>
    </xs:complexType>
</xs:element>

<xs:element name="EQUATEX">
    <xs:annotation>
        <xs:documentation>
            Equation avec la syntaxe TeX: texte
            et image PNG encodée en base64 à l'intérieur de l'élément.
        </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
        <xs:attribute name="texte" type="xs:string" use="required"/>
        <xs:attribute name="label" type="xs:string" use="optional"/>
    </xs:complexType>
</xs:element>

<xs:element name="GLOSSAIRE">
    <xs:annotation>
        <xs:documentation>
        Renvoi au glossaire des mots importants ou jargonneux,
        et éventuelle proposition de définition pour le glossaire.
        Les attributs définissent le mot affiché et le mot de référence,
        et le contenu de l'élément correspond à sa définition.
        </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
            <xs:group ref="styletexte"/>
        </xs:choice>
        <xs:attribute name="mot" type="xs:string" use="required">
            <xs:annotation>
                <xs:documentation>
                Mot affiché.
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>
        <xs:attribute name="ref" type="xs:string" use="optional">
            <xs:annotation>
                <xs:documentation>
                Mot de référence dans le glossaire, s'il est différent du mot affiché.
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>
    </xs:complexType>
</xs:element>

<xs:element name="BIBLIOGRAPHIE">
    <xs:annotation>
        <xs:documentation>
        Liste de références bibliographiques et webographiques.
        </xs:documentation>
    </xs:annotation>
    <xs:complexType>
        <xs:choice minOccurs="1" maxOccurs="unbounded">
            <xs:element ref="REFOUVRAGE"/>
            <xs:element ref="REFARTICLE"/>
            <xs:element ref="REFWEB"/>
        </xs:choice>
        <xs:attribute name="titre" type="xs:string" use="optional"/>
    </xs:complexType>
</xs:element>

<xs:element name="REFOUVRAGE">
    <xs:annotation>
        <xs:documentation>
            Référence bibliographique pour un ouvrage.
        </xs:documentation>
    </xs:annotation>
    <xs:complexType>
        <xs:attribute name="titre" type="xs:string" use="required"/>
        <xs:attribute name="sous-titre" type="xs:string" use="optional"/>
        <xs:attribute name="premierAuteur" use="required">
            <xs:annotation>
                <xs:documentation>
                    Ecrire sous la forme "NOM, Prénom".
                </xs:documentation>
            </xs:annotation>
            <xs:simpleType>
                <xs:restriction base="xs:string">
                    <xs:pattern value="[A-Z \-]+,.+"/>
                </xs:restriction>
            </xs:simpleType>
        </xs:attribute>
        <xs:attribute name="autresAuteurs" type="xs:string" use="optional"/>
        <xs:attribute name="volume" type="xs:string" use="optional"/>
        <xs:attribute name="lieu" type="xs:string" use="optional"/>
        <xs:attribute name="editeur" type="xs:string" use="required"/>
        <xs:attribute name="annee" type="xs:string" use="required"/>
        <xs:attribute name="pagination" type="xs:string" use="optional"/>
        <xs:attribute name="collection" type="xs:string" use="optional"/>
        <xs:attribute name="isbn" use="optional">
            <xs:simpleType>
                <xs:restriction base="xs:string">
                    <xs:pattern value="[0-9\-]+"/>
                </xs:restriction>
            </xs:simpleType>
        </xs:attribute>
        <xs:attribute name="url" type="typeURL" use="optional"/>
        <xs:attribute name="autre" type="xs:string" use="optional"/>
    </xs:complexType>
</xs:element>

<xs:element name="REFARTICLE">
    <xs:annotation>
        <xs:documentation>
            Référence bibliographique pour un article de périodique.
        </xs:documentation>
    </xs:annotation>
    <xs:complexType>
        <xs:attribute name="titreArticle" type="xs:string" use="required"/>
        <xs:attribute name="titrePeriodique" type="xs:string" use="required"/>
        <xs:attribute name="premierAuteur" use="required">
            <xs:annotation>
                <xs:documentation>
                    Ecrire sous la forme "NOM, Prénom".
                </xs:documentation>
            </xs:annotation>
            <xs:simpleType>
                <xs:restriction base="xs:string">
                    <xs:pattern value="[A-Z \-]+,.+"/>
                </xs:restriction>
            </xs:simpleType>
        </xs:attribute>
        <xs:attribute name="autresAuteurs" type="xs:string" use="optional"/>
        <xs:attribute name="annee" type="xs:positiveInteger" use="required"/>
        <xs:attribute name="volume" type="xs:string" use="optional"/>
        <xs:attribute name="numero" type="xs:string" use="required"/>
        <xs:attribute name="pagination" type="xs:string" use="required"/>
        <xs:attribute name="url" type="typeURL" use="optional"/>
        <xs:attribute name="autre" type="xs:string" use="optional"/>
    </xs:complexType>
</xs:element>

<xs:element name="REFWEB">
    <xs:annotation>
        <xs:documentation>
            Référence pour une page web.
        </xs:documentation>
    </xs:annotation>
    <xs:complexType>
        <xs:attribute name="url" type="typeURL" use="required">
            <xs:annotation>
                <xs:documentation>
                    URL de la page web qui doit être référencée dans la webographie. Doit commencer par "http://".
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>
        <xs:attribute name="auteurs" type="xs:string" use="required">
            <xs:annotation>
                <xs:documentation>
                    Auteurs ou collectivité.
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>
        <xs:attribute name="titre" type="xs:string" use="required"/>
        <xs:attribute name="titreSite" type="xs:string" use="optional"/>
        <xs:attribute name="dateConsultation" type="xs:string" use="optional"/>
        <xs:attribute name="description" type="xs:string" use="optional"/>
    </xs:complexType>
</xs:element>

<xs:element name="EM">
    <xs:annotation>
        <xs:documentation>
        Emphase
        </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
            <xs:element ref="SUP"/>
            <xs:element ref="SUB"/>
            <xs:element ref="NBSP"/>
        </xs:choice>
    </xs:complexType>
</xs:element>

<xs:element name="CODE">
    <xs:annotation>
        <xs:documentation>
        Code informatique (généralement affiché avec une police dont les lettres ont la même taille)
        </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
            <xs:element ref="NBSP"/>
        </xs:choice>
    </xs:complexType>
</xs:element>

<xs:element name="SUP" type="xs:string">
    <xs:annotation>
        <xs:documentation>
        Exposant
        </xs:documentation>
    </xs:annotation>
</xs:element>

<xs:element name="SUB" type="xs:string">
    <xs:annotation>
        <xs:documentation>
        Indice
        </xs:documentation>
    </xs:annotation>
</xs:element>

<xs:element name="NBSP">
    <xs:annotation>
        <xs:documentation>
        Espace insécable
        </xs:documentation>
    </xs:annotation>
</xs:element>

</xs:schema>

Index: modules/damieng/graphical_editor/daxe/web/config/XPAGES_config.xml
+++ modules/damieng/graphical_editor/daxe/web/config/XPAGES_config.xml
<?xml version="1.0" encoding="UTF-8"?><CONFIG_JAXE>
<LANGAGE>
<FICHIER_SCHEMA nom="XPAGES.xsd"/>
<RACINE element="XPAGES"/>
</LANGAGE>

<ENREGISTREMENT>
<ENCODAGE>UTF-8</ENCODAGE>
</ENREGISTREMENT>

<MENUS>
<MENU nom="Divisions">
<MENU_INSERTION nom="INFORMATIONS" type_noeud="element"/>
<MENU_INSERTION nom="PAGE" type_noeud="element"/>
<MENU_INSERTION nom="REFXPAGES" type_noeud="element"/>
<MENU_INSERTION nom="SECTION" type_noeud="element"/>
<MENU_INSERTION nom="PARAGRAPHE" raccourci="P" type_noeud="element"/>
</MENU>
<MENU nom="Listes/Tableaux">
<MENU_INSERTION nom="TABLEAU" type_noeud="element"/>
<MENU_INSERTION nom="LISTE" type_noeud="element"/>
<MENU_INSERTION nom="ITEM" type_noeud="element"/>
</MENU>
<MENU nom="Figures">
<MENU_INSERTION nom="ENVIMAGE" type_noeud="element"/>
<MENU_INSERTION nom="FICHIER" type_noeud="element"/>
<MENU_INSERTION nom="FORMATS"/>
<MENU_INSERTION nom="LEGENDE" type_noeud="element"/>
<MENU_INSERTION nom="CREDIT" type_noeud="element"/>
</MENU>
<MENU nom="Interactif">
<MENU_INSERTION nom="APPLICATION"/>
<MENU_INSERTION nom="PARAM" type_noeud="element"/>
</MENU>
<MENU nom="Exercices">
<MENU_INSERTION nom="EXERCICE"/>
<MENU_INSERTION nom="ENONCE" type_noeud="element"/>
<MENU_INSERTION nom="DIFFICULTE"/>
<MENU_INSERTION nom="TEMPS"/>
<MENU_INSERTION nom="QUESTION"/>
<MENU_INSERTION nom="AIDE" type_noeud="element"/>
<MENU_INSERTION nom="SOLUTION"/>
<MENU_INSERTION nom="POINTS"/>
<MENU_INSERTION nom="QCM" type_noeud="element"/>
<MENU_INSERTION nom="ENONCEQCM"/>
<MENU_INSERTION nom="QUESTIONQCM" type_noeud="element"/>
<MENU_INSERTION nom="REPONSEQCM"/>
<MENU_INSERTION nom="TEXTEREP" type_noeud="element"/>
<MENU_INSERTION nom="COMMENTAIREREP" type_noeud="element"/>
</MENU>
<MENU nom="Autres">
<MENU_INSERTION nom="LIENPAGE" type_noeud="element"/>
<MENU_INSERTION nom="LIENWEB" type_noeud="element"/>
<MENU_INSERTION nom="LIENIMAGE"/>
<MENU_INSERTION nom="GLOSSAIRE" type_noeud="element"/>
<MENU_INSERTION nom="BIBLIOGRAPHIE"/>
<MENU_INSERTION nom="REFOUVRAGE"/>
<MENU_INSERTION nom="REFARTICLE"/>
<MENU_INSERTION nom="REFWEB"/>
<MENU_INSERTION nom="SYMBOLE"/>
<MENU_INSERTION nom="EQUATION" raccourci="E" type_noeud="element"/>
<MENU_INSERTION nom="EQUATEX" raccourci="T" type_noeud="element"/>
<MENU_INSERTION nom="CODE" type_noeud="element"/>
<MENU_INSERTION nom="COMMENTAIRE" type_noeud="element"/>
<MENU_INSERTION nom="NBSP" type_noeud="element"/>
</MENU>
<MENU nom="Style">
<MENU_FONCTION classe="jaxe.FonctionNormal" nom="Normal"/>
<MENU_INSERTION nom="SUP" type_noeud="element"/>
<MENU_INSERTION nom="SUB" type_noeud="element"/>
<MENU_INSERTION nom="EM" type_noeud="element"/>
</MENU>
</MENUS>

<AFFICHAGE_NOEUDS>
<AFFICHAGE_ELEMENT element="XPAGES" type="division"/>
<AFFICHAGE_ELEMENT element="PAGE" type="division">
<PARAMETRE nom="titreAtt" valeur="titre"/>
<AFFICHAGE_ATTRIBUT attribut="role">
<VALEUR_SUGGEREE>autre</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>introduction</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>conclusion</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>prerequis</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>objectifs</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>rappel</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>definition</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>demonstration</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>exemple</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>conseil</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>remarque</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>attention</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>complement</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>methode</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>activite</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>bibliographie</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>citation</VALEUR_SUGGEREE>
</AFFICHAGE_ATTRIBUT>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="REFXPAGES" type="vide">
<PARAMETRE nom="titreAtt" valeur="label"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="SECTION" type="division">
<PARAMETRE nom="titreAtt" valeur="titre"/>
<AFFICHAGE_ATTRIBUT attribut="role">
<VALEUR_SUGGEREE>autre</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>introduction</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>conclusion</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>prerequis</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>objectifs</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>rappel</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>definition</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>demonstration</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>exemple</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>conseil</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>remarque</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>attention</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>complement</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>methode</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>activite</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>bibliographie</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>citation</VALEUR_SUGGEREE>
</AFFICHAGE_ATTRIBUT>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="PARAGRAPHE" type="zone"/>
<AFFICHAGE_ELEMENT element="INFORMATIONS" type="formulaire"/>
<AFFICHAGE_ELEMENT element="LABEL" type="formulaire"/>
<AFFICHAGE_ELEMENT element="TITRE" type="formulaire"/>
<AFFICHAGE_ELEMENT element="AUTEUR" type="formulaire"/>
<AFFICHAGE_ELEMENT element="DATECRE" type="formulaire"/>
<AFFICHAGE_ELEMENT element="DATEMAJ" type="formulaire"/>
<!--<AFFICHAGE_ELEMENT element="LOGO" type="vide">
<PARAMETRE nom="srcAtt" valeur="fichier"/>
</AFFICHAGE_ELEMENT>-->
<AFFICHAGE_ELEMENT element="LOGO" type="formulaire"/>
<AFFICHAGE_ELEMENT element="INTERFACE" type="formulaire">
<VALEUR_SUGGEREE>azur</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>bois</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>etoiles</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>marbre</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>parchemin</VALEUR_SUGGEREE>
<VALEUR_SUGGEREE>peinture</VALEUR_SUGGEREE>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="RUBRIQUES" type="formulaire"/>
<AFFICHAGE_ELEMENT element="LANGUE" type="formulaire"/>
<AFFICHAGE_ELEMENT element="TABLEAU" type="tabletexte">
<PARAMETRE nom="trTag" valeur="TR"/>
<PARAMETRE nom="tdTag" valeur="TD"/>
<PARAMETRE nom="thTag" valeur="TH"/>
<PARAMETRE nom="colspanAttr" valeur="colspan"/>
<PARAMETRE nom="rowspanAttr" valeur="rowspan"/>
<PARAMETRE nom="alignAttr" valeur="align"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="TR" type="zone"/>
<AFFICHAGE_ELEMENT element="TD" type="string"/>
<AFFICHAGE_ELEMENT element="TH" type="string">
<PARAMETRE nom="style" valeur="GRAS"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="LISTE" type="liste">
<PARAMETRE nom="typeListe" valeur="POINTS"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="ITEM" type="item"/>
<AFFICHAGE_ELEMENT element="ENVIMAGE" type="zone">
<PARAMETRE nom="titreAtt" valeur="titre"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="FICHIER" type="fichier">
<PARAMETRE nom="srcAtt" valeur="nom"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="FORMATS" type="zone">
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="LEGENDE" type="zone"/>
<AFFICHAGE_ELEMENT element="CREDIT" type="zone"/>
<AFFICHAGE_ELEMENT element="APPLICATION" type="zone">
<PARAMETRE nom="titreAtt" valeur="titre"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="PARAM" type="vide">
<PARAMETRE nom="titreAtt" valeur="nom"/>
</AFFICHAGE_ELEMENT>

<AFFICHAGE_ELEMENT element="EXERCICE" type="formulaire"/>
<AFFICHAGE_ELEMENT element="ENONCE" type="zone"/>
<AFFICHAGE_ELEMENT element="DIFFICULTE" type="formulaire"/>
<AFFICHAGE_ELEMENT element="TEMPS" type="formulaire"/>
<AFFICHAGE_ELEMENT element="QUESTION" type="formulaire"/>
<AFFICHAGE_ELEMENT element="AIDE" type="zone"/>
<AFFICHAGE_ELEMENT element="SOLUTION" type="zone"/>
<AFFICHAGE_ELEMENT element="POINTS" type="formulaire"/>
<AFFICHAGE_ELEMENT element="QCM" type="formulaire"/>
<AFFICHAGE_ELEMENT element="QUESTIONQCM" type="formulaire"/>
<AFFICHAGE_ELEMENT element="ENONCEQCM" type="zone"/>
<AFFICHAGE_ELEMENT element="REPONSEQCM" type="formulaire">
<PARAMETRE nom="attributsVisibles" valeur="true"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="TEXTEREP" type="zone"/>
<AFFICHAGE_ELEMENT element="COMMENTAIREREP" type="zone"/>

<AFFICHAGE_ELEMENT element="LIENPAGE" type="string"/>
<AFFICHAGE_ELEMENT element="LIENWEB" type="string"/>
<AFFICHAGE_ELEMENT element="LIENIMAGE" type="string"/>
<AFFICHAGE_ELEMENT element="GLOSSAIRE" type="string">
<PARAMETRE nom="titreAtt" valeur="mot"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="BIBLIOGRAPHIE" type="zone">
<PARAMETRE nom="titreAtt" valeur="titre"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="REFOUVRAGE" type="vide">
<PARAMETRE nom="titreAtt" valeur="titre"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="REFARTICLE" type="vide">
<PARAMETRE nom="titreAtt" valeur="titreArticle"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="REFWEB" type="vide">
<PARAMETRE nom="titreAtt" valeur="titre"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="SYMBOLE" type="symbole2"/>
<AFFICHAGE_ELEMENT element="EQUATION" type="equationmem">
<PARAMETRE nom="srcAtt" valeur="image"/>
<PARAMETRE nom="texteAtt" valeur="texte"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="EQUATEX" type="equatexmem">
<PARAMETRE nom="serveur" valeur="//media4.obspm.fr/serveurtex/tex.php"/>
<PARAMETRE nom="texteAtt" valeur="texte"/>
<PARAMETRE nom="labelAtt" valeur="label"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="CODE" type="string">
<PARAMETRE nom="police" valeur="Monospaced"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="COMMENTAIRE" type="zone"/>
<AFFICHAGE_ELEMENT element="NBSP" type="vide"/>
<AFFICHAGE_ELEMENT element="SUP" type="style">
<PARAMETRE nom="style" valeur="EXPOSANT"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="SUB" type="style">
<PARAMETRE nom="style" valeur="INDICE"/>
</AFFICHAGE_ELEMENT>
<AFFICHAGE_ELEMENT element="EM" type="string">
<PARAMETRE nom="style" valeur="ITALIQUE"/>
</AFFICHAGE_ELEMENT>
</AFFICHAGE_NOEUDS>

<EXPORTS>
<EXPORT nom="Site public" sortie="HTML">
<FICHIER_XSL nom="XPAGES1.xsl">
<PARAMETRE nom="jaxe-uri-xml" valeur="*"/>
</FICHIER_XSL>
<FICHIER_XSL nom="XPAGES2.xsl">
<PARAMETRE nom="jaxe-fichier-xml" valeur="*"/>
<PARAMETRE nom="jaxe-fichier-xsl" valeur="*"/>
<PARAMETRE nom="jaxe-fichier-destination" valeur="*"/>
<PARAMETRE nom="jaxe-uri-destination" valeur="*"/>
</FICHIER_XSL>
<FICHIER_XSL nom="XPAGES3.xsl">
<PARAMETRE nom="jaxe-fichier-xml" valeur="*"/>
<PARAMETRE nom="jaxe-uri-xsl" valeur="*"/>
<PARAMETRE nom="jaxe-uri-xml" valeur="*"/>
<PARAMETRE nom="jaxe-fichier-destination" valeur="*"/>
<PARAMETRE nom="jaxe-uri-destination" valeur="*"/>
<PARAMETRE nom="affichage" valeur="web"/>
<PARAMETRE nom="sortie" valeur="public"/>
</FICHIER_XSL>
</EXPORT>
<EXPORT nom="Site de production" sortie="HTML">
<FICHIER_XSL nom="XPAGES1.xsl">
<PARAMETRE nom="jaxe-uri-xml" valeur="*"/>
</FICHIER_XSL>
<FICHIER_XSL nom="XPAGES2.xsl">
<PARAMETRE nom="jaxe-fichier-xml" valeur="*"/>
<PARAMETRE nom="jaxe-fichier-xsl" valeur="*"/>
<PARAMETRE nom="jaxe-fichier-destination" valeur="*"/>
<PARAMETRE nom="jaxe-uri-destination" valeur="*"/>
</FICHIER_XSL>
<FICHIER_XSL nom="XPAGES3.xsl">
<PARAMETRE nom="jaxe-fichier-xml" valeur="*"/>
<PARAMETRE nom="jaxe-uri-xsl" valeur="*"/>
<PARAMETRE nom="jaxe-uri-xml" valeur="*"/>
<PARAMETRE nom="jaxe-fichier-destination" valeur="*"/>
<PARAMETRE nom="jaxe-uri-destination" valeur="*"/>
<PARAMETRE nom="affichage" valeur="web"/>
<PARAMETRE nom="sortie" valeur="production"/>
</FICHIER_XSL>
</EXPORT>
</EXPORTS>

<STRINGS langue="fr">
<DESCRIPTION_CONFIG>Ensemble de pages sur un sujet donné</DESCRIPTION_CONFIG>

<STRINGS_MENU menu="Divisions">
<TITRE>Divisions</TITRE>
<STRINGS_MENU menu="PARAGRAPHE">
<TITRE>Paragraphe</TITRE>
</STRINGS_MENU>
</STRINGS_MENU>
<STRINGS_MENU menu="Listes/Tableaux">
<TITRE>Listes/Tableaux</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="Figures">
<TITRE>Figures</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="Interactif">
<TITRE>Interactif</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="Autres">
<TITRE>Autres</TITRE>
<STRINGS_MENU menu="NBSP">
<TITRE>Espace insécable</TITRE>
</STRINGS_MENU>
</STRINGS_MENU>
<STRINGS_MENU menu="Style">
<TITRE>Style</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="Normal">
<TITRE>Sans style</TITRE>
</STRINGS_MENU>

<STRINGS_ELEMENT element="XPAGES">
<TITRE>Ensemble XPAGES</TITRE>
<DOCUMENTATION>
Un ensemble de pages qui se suivent, sur un sujet donné.
</DOCUMENTATION>
</STRINGS_ELEMENT>

<STRINGS_ELEMENT element="INFORMATIONS">
<TITRE>Informations</TITRE>
<DOCUMENTATION>
Informations sur le document. Le label et le titre sont obligatoires.
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="LABEL">
<TITRE>Label</TITRE>
<DOCUMENTATION>
Identifiant pour cette contribution.
Ne peut contenir que des lettres de a à z, des chiffres de 0 à 9, et le caractère "-".
Le nom du fichier XML et le nom du dossier doivent correspondre au label.
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="TITRE">
<TITRE>Titre</TITRE>
<DOCUMENTATION>
Titre de l'ensemble de pages.
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="AUTEUR">
<TITRE>Auteur</TITRE>
<DOCUMENTATION>
Un auteur
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="DATECRE">
<TITRE>Date de création</TITRE>
<DOCUMENTATION>
Date de création, au format JJ/MM/AAAA
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="DATEMAJ">
<TITRE>Date de mise à jour</TITRE>
<DOCUMENTATION>
Date de mise à jour, au format JJ/MM/AAAA
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="LOGO">
<TITRE>Logo</TITRE>
<DOCUMENTATION>
Fichier image d'un logo à utiliser pour l'interface du site.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="fichier">
<TITRE>fichier</TITRE>
<DOCUMENTATION>
Chemin vers le fichier de l'image, relatif au fichier XML.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="lien">
<TITRE>lien</TITRE>
<DOCUMENTATION>
Lien optionnel d'une page web vers laquelle aller quand on clique sur le logo.
Doit commencer par "http://".
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="INTERFACE">
<TITRE>Interface</TITRE>
<DOCUMENTATION>
Interface des pages.
</DOCUMENTATION>
<TITRE_VALEUR valeur="azur">azur</TITRE_VALEUR>
<TITRE_VALEUR valeur="bois">bois</TITRE_VALEUR>
<TITRE_VALEUR valeur="etoiles">étoiles</TITRE_VALEUR>
<TITRE_VALEUR valeur="marbre">marbre</TITRE_VALEUR>
<TITRE_VALEUR valeur="parchemin">parchemin</TITRE_VALEUR>
<TITRE_VALEUR valeur="peinture">peinture</TITRE_VALEUR>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="RUBRIQUES">
<TITRE>Rubriques</TITRE>
<DOCUMENTATION>
Afficher les rubriques ou non. Par défaut, utilise la valeur de l'élément parent.
L'élément XPAGES de plus haut niveau a la valeur "oui" par défaut.
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="LANGUE">
<TITRE>Langue</TITRE>
<DOCUMENTATION>
Code de la langue à utiliser pour l'interface utilisateur.
</DOCUMENTATION>
</STRINGS_ELEMENT>

<STRINGS_ELEMENT element="PAGE">
<TITRE>Page</TITRE>
<DOCUMENTATION>
Page écran, d'environ 1000 caractères.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="label">
<TITRE>label</TITRE>
<DOCUMENTATION>
Label de la page (identifiant caché permettant de faire un lien vers cette page).
Ne doit être composé que de lettres minuscules de a à z.
Utiliser le label "index" pour la page d'entrée du site.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="titre">
<TITRE>titre</TITRE>
<DOCUMENTATION>
Titre de la page.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="role">
<TITRE>rôle</TITRE>
<DOCUMENTATION>
Rôle sémantique de la page : sens qu'on lui donne.
</DOCUMENTATION>
<TITRE_VALEUR valeur="autre">autre</TITRE_VALEUR>
<TITRE_VALEUR valeur="introduction">introduction</TITRE_VALEUR>
<TITRE_VALEUR valeur="conclusion">conclusion</TITRE_VALEUR>
<TITRE_VALEUR valeur="prerequis">prérequis</TITRE_VALEUR>
<TITRE_VALEUR valeur="objectifs">objectifs</TITRE_VALEUR>
<TITRE_VALEUR valeur="rappel">rappel</TITRE_VALEUR>
<TITRE_VALEUR valeur="definition">définition</TITRE_VALEUR>
<TITRE_VALEUR valeur="demonstration">démonstration</TITRE_VALEUR>
<TITRE_VALEUR valeur="exemple">exemple</TITRE_VALEUR>
<TITRE_VALEUR valeur="conseil">conseil</TITRE_VALEUR>
<TITRE_VALEUR valeur="remarque">remarque</TITRE_VALEUR>
<TITRE_VALEUR valeur="attention">attention</TITRE_VALEUR>
<TITRE_VALEUR valeur="complement">complément</TITRE_VALEUR>
<TITRE_VALEUR valeur="methode">méthode</TITRE_VALEUR>
<TITRE_VALEUR valeur="activite">activité</TITRE_VALEUR>
<TITRE_VALEUR valeur="bibliographie">bibliographie</TITRE_VALEUR>
<TITRE_VALEUR valeur="citation">citation</TITRE_VALEUR>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="REFXPAGES">
<TITRE>Réf. document XPAGES</TITRE>
<DOCUMENTATION>
Référence vers un autre ensemble de pages, dont le titre sera affiché
dans le sommaire de cet ensemble.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="label">
<TITRE>label</TITRE>
<DOCUMENTATION>
Label du document XPAGES référencé.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="SECTION">
<TITRE>Section</TITRE>
<DOCUMENTATION>
Une section à l'intérieur d'une page, qui peut avoir un titre.
Le rôle permet de donner un sens à la section, pour des transformations XML ultérieures ou un affichage particulier.
L'importance "grande" permet de mettre en valeur la section par rapport au reste du texte.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="titre">
<TITRE>titre</TITRE>
<DOCUMENTATION>
Titre de la section.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="role">
<TITRE>rôle</TITRE>
<DOCUMENTATION>
Rôle sémantique de la section : sens qu'on lui donne.
</DOCUMENTATION>
<TITRE_VALEUR valeur="autre">autre</TITRE_VALEUR>
<TITRE_VALEUR valeur="introduction">introduction</TITRE_VALEUR>
<TITRE_VALEUR valeur="conclusion">conclusion</TITRE_VALEUR>
<TITRE_VALEUR valeur="prerequis">prérequis</TITRE_VALEUR>
<TITRE_VALEUR valeur="objectifs">objectifs</TITRE_VALEUR>
<TITRE_VALEUR valeur="rappel">rappel</TITRE_VALEUR>
<TITRE_VALEUR valeur="definition">définition</TITRE_VALEUR>
<TITRE_VALEUR valeur="demonstration">démonstration</TITRE_VALEUR>
<TITRE_VALEUR valeur="exemple">exemple</TITRE_VALEUR>
<TITRE_VALEUR valeur="conseil">conseil</TITRE_VALEUR>
<TITRE_VALEUR valeur="remarque">remarque</TITRE_VALEUR>
<TITRE_VALEUR valeur="attention">attention</TITRE_VALEUR>
<TITRE_VALEUR valeur="complement">complément</TITRE_VALEUR>
<TITRE_VALEUR valeur="methode">méthode</TITRE_VALEUR>
<TITRE_VALEUR valeur="activite">activité</TITRE_VALEUR>
<TITRE_VALEUR valeur="bibliographie">bibliographie</TITRE_VALEUR>
<TITRE_VALEUR valeur="citation">citation</TITRE_VALEUR>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="importance">
<TITRE>importance</TITRE>
<DOCUMENTATION>
Importance de la section. Une section importante pourra être mise en évidence
dans le site web généré.
</DOCUMENTATION>
<TITRE_VALEUR valeur="normale">normale</TITRE_VALEUR>
<TITRE_VALEUR valeur="grande">grande</TITRE_VALEUR>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="PARAGRAPHE">
<TITRE>P</TITRE>
<DOCUMENTATION>
Un paragraphe (quelques lignes sans saut de ligne).
</DOCUMENTATION>
</STRINGS_ELEMENT>

<STRINGS_ELEMENT element="TABLEAU">
<TITRE>Tableau</TITRE>
<DOCUMENTATION>
Tableau.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="titre">
<TITRE>titre</TITRE>
<DOCUMENTATION>
Titre du tableau
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="TR">
<TITRE>Ligne de table</TITRE>
<DOCUMENTATION>
Ligne d'un tableau
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="TD">
<TITRE>Cellule de table</TITRE>
<DOCUMENTATION>
Cellule d'un tableau
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="align">
<TITRE>align</TITRE>
<DOCUMENTATION>
Alignement du texte de la cellule
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="colspan">
<TITRE>colspan</TITRE>
<DOCUMENTATION>
Regroupement horizontal de cellules :
nombres de cellules à regrouper à droite de celle-ci
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="rowspan">
<TITRE>rowspan</TITRE>
<DOCUMENTATION>
Regroupement vertical de cellules :
nombres de cellules à regrouper en bas de celle-ci
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="TH">
<TITRE>Cellule entête de table</TITRE>
<DOCUMENTATION>
Entête d'une colonne
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="align">
<TITRE>align</TITRE>
<DOCUMENTATION>
Alignement du texte de la cellule
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="colspan">
<TITRE>colspan</TITRE>
<DOCUMENTATION>
Regroupement horizontal de cellules :
nombres de cellules à regrouper à droite de celle-ci
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="rowspan">
<TITRE>rowspan</TITRE>
<DOCUMENTATION>
Regroupement vertical de cellules :
nombres de cellules à regrouper en bas de celle-ci
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="LISTE">
<TITRE>Liste</TITRE>
<DOCUMENTATION>
Liste avec des puces ou des numéros
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="type">
<TITRE>type</TITRE>
<TITRE_VALEUR valeur="puces">puces</TITRE_VALEUR>
<TITRE_VALEUR valeur="numéros">numéros</TITRE_VALEUR>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="ITEM">
<TITRE>Elément de liste</TITRE>
<DOCUMENTATION>
Elément d'une liste
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="ENVIMAGE">
<TITRE>Environnement d'image</TITRE>
<DOCUMENTATION>
Environnement pour des images ou animations.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="titre">
<TITRE>titre</TITRE>
<DOCUMENTATION>
Titre de l'image ou animation.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="localisation">
<TITRE>localisation</TITRE>
<DOCUMENTATION>
Positionnement souhaité pour l'image :
- texte : dans le texte à la taille d'origine
- page : dans la page à côté du texte, réduit si nécessaire
- icône : sous forme de petite icône à côté du paragraphe qui suit
</DOCUMENTATION>
<TITRE_VALEUR valeur="texte">texte</TITRE_VALEUR>
<TITRE_VALEUR valeur="page">page</TITRE_VALEUR>
<TITRE_VALEUR valeur="icône">icône</TITRE_VALEUR>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="FICHIER">
<TITRE>Fichier</TITRE>
<DOCUMENTATION>
Fichier image ou animation à inclure dans le texte,
ou à localiser dans une page qui s'ouvrira à part du texte.
Si le contenu du fichier doit être accompagné d'une légende,
il est nécessaire de passer par l'élément ENVIMAGE.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="nom">
<TITRE>nom</TITRE>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="FORMATS">
<TITRE>Formats</TITRE>
<DOCUMENTATION>
Permet de spécifier plusieurs fichiers, avec des formats différents, pour un même media.
Un seul fichier sera affiché, choisi en fonction des capacités de l'environnement d'affichage.
Cet élément n'est utile qu'avec des formats qui ne sont pas lisibles partout, par exemple pour des vidéos.
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="LEGENDE">
<TITRE>Légende</TITRE>
<DOCUMENTATION>
Légende d'un tableau ou d'une figure
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="CREDIT">
<TITRE>Crédit</TITRE>
<DOCUMENTATION>
crédit des documents
</DOCUMENTATION>
</STRINGS_ELEMENT>

<STRINGS_ELEMENT element="APPLICATION">
<TITRE>Application interactive</TITRE>
<DOCUMENTATION>
Application interactive. Remplace les éléments APPLET et FLASH en généralisant le concept.
L'attribut type précise le type d'application (applet Java, Flash ou HTML/Javascript).
Le fichier indiqué doit être un .jar pour les applets Java, un .swf pour les applications Flash, et un .html pour les applications Javascript.
Les applets Java doivent avoir un paramètre "code" avec le nom de la classe.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="type">
<TITRE>type</TITRE>
<DOCUMENTATION>
Type d'application (applet Java, Flash ou HTML/Javascript).
</DOCUMENTATION>
<TITRE_VALEUR valeur="applet">applet Java</TITRE_VALEUR>
<TITRE_VALEUR valeur="flash">Flash</TITRE_VALEUR>
<TITRE_VALEUR valeur="html">HTML/Javascript</TITRE_VALEUR>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="titre">
<TITRE>titre</TITRE>
<DOCUMENTATION>
Titre de l'application.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="fichier">
<TITRE>fichier</TITRE>
<DOCUMENTATION>
Fichier de l'application.
Doit être un .jar pour les applets Java, un .swf pour les applications Flash, et un .html pour les applications Javascript.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="contribution">
<TITRE>contribution</TITRE>
<DOCUMENTATION>
Contribution du fichier de l'application.
Permet de ne pas avoir à recopier plusieurs fois une application utilisée dans plusieurs contributions.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="largeur">
<TITRE>largeur</TITRE>
<DOCUMENTATION>
Largeur de la zone de l'application, en pixels.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="hauteur">
<TITRE>hauteur</TITRE>
<DOCUMENTATION>
Hauteur de la zone de l'application, en pixels.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="localisation">
<TITRE>localisation</TITRE>
<DOCUMENTATION>
Positionnement souhaité pour l'application :
- texte : dans le texte
- page : dans la page à côté du texte
- icône : accessible à partir d'une petite icône à côté du paragraphe qui suit
</DOCUMENTATION>
<TITRE_VALEUR valeur="texte">texte</TITRE_VALEUR>
<TITRE_VALEUR valeur="page">page</TITRE_VALEUR>
<TITRE_VALEUR valeur="icône">icône</TITRE_VALEUR>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="PARAM">
<TITRE>Paramètre</TITRE>
<DOCUMENTATION>
Paramètre d'applet ou d'objet Flash
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="nom">
<TITRE>nom</TITRE>
<DOCUMENTATION>
Nom du paramètre.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="valeur">
<TITRE>valeur</TITRE>
<DOCUMENTATION>
Valeur du paramètre.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>

<STRINGS_ELEMENT element="EXERCICE">
<TITRE>Exercice</TITRE>
<DOCUMENTATION>
Exercice de type questions-réponses
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="titre">
<TITRE>titre</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="type">
<TITRE>type</TITRE>
<TITRE_VALEUR valeur="évaluation">évaluation</TITRE_VALEUR>
<TITRE_VALEUR valeur="auto-évaluation">auto-évaluation</TITRE_VALEUR>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="DIFFICULTE">
<TITRE>Difficulté</TITRE>
<DOCUMENTATION>
Indication du niveau de difficulté :
* : facile, ou application directe du cours
** : moyen
*** : difficile
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="TEMPS">
<TITRE>Temps</TITRE>
<DOCUMENTATION>
Temps indicatif nécessaire pour résoudre l'exercice
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="QUESTION">
<TITRE>Question</TITRE>
<DOCUMENTATION>
Question d'un exercice, avec un énoncé, éventuellement des aides, une solution, et éventuellement des points.
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="ENONCE">
<TITRE>Enoncé</TITRE>
<DOCUMENTATION>
Enoncé d'un exercice ou d'une question
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="AIDE">
<TITRE>Aide</TITRE>
<DOCUMENTATION>
Aide d'un exercice
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="SOLUTION">
<TITRE>Solution</TITRE>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="POINTS">
<TITRE>Points</TITRE>
<DOCUMENTATION>
Nombre de points gagnés si la réponse est correcte.
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="QCM">
<TITRE>QCM</TITRE>
<DOCUMENTATION>
Questionnaire à choix multiples, avec des commentaires affichés après le choix de l'utilisateur.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="titre">
<TITRE>titre</TITRE>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="QUESTIONQCM">
<TITRE>Question de QCM</TITRE>
<DOCUMENTATION>
Question d'un QCM, avec un énoncé, éventuellement des aides, et les réponses possibles.
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="ENONCEQCM">
<TITRE>Enoncé de question</TITRE>
<DOCUMENTATION>
Enoncé d'une question de QCM (texte court)
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="REPONSEQCM">
<TITRE>Réponse de QCM</TITRE>
<DOCUMENTATION>
Réponse possible à un QCM.
Contient le texte de la réponse et un commentaire à afficher si la réponse donnée est incorrecte.
L'attribut bonne indique si la réponse est bonne ou pas.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="bonne">
<TITRE>bonne</TITRE>
<TITRE_VALEUR valeur="non">non</TITRE_VALEUR>
<TITRE_VALEUR valeur="oui">oui</TITRE_VALEUR>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="TEXTEREP">
<TITRE>Texte de réponse</TITRE>
<DOCUMENTATION>
Texte de la réponse
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="COMMENTAIREREP">
<TITRE>Commentaire de réponse</TITRE>
<DOCUMENTATION>
Commentaire à afficher après le choix de l'utilisateur (en particulier si la réponse donnée est incorrecte).
</DOCUMENTATION>
</STRINGS_ELEMENT>

<STRINGS_ELEMENT element="LIENPAGE">
<TITRE>Lien vers page interne</TITRE>
<DOCUMENTATION>
Lien vers une page de la même contribution (si contribution est vide),
ou vers une page d'une autre contribution (avec contribution = le label de la contribution).
Doit contenir du texte ou un fichier sur lequel sera placé le lien.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="contribution">
<TITRE>contribution</TITRE>
<DOCUMENTATION>
Le label de la contribution. Vide si on veut faire un lien vers la même contribution.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="page">
<TITRE>page</TITRE>
<DOCUMENTATION>
Le label de la page. Vide si on veut faire un lien vers la première page de la contribution.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="LIENWEB">
<TITRE>Lien web externe</TITRE>
<DOCUMENTATION>
Lien externe (avec url commençant par "http://").
Doit contenir du texte ou un fichier sur lequel sera placé le lien.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="url">
<TITRE>url</TITRE>
<DOCUMENTATION>
Adresse de la page web. Doit commencer par "http://".
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="LIENIMAGE">
<TITRE>Lien vers image</TITRE>
<DOCUMENTATION>
Lien vers un environnement d'image dans la même page.
Doit contenir du texte ou un fichier sur lequel sera placé le lien.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="url">
<TITRE>label</TITRE>
<DOCUMENTATION>
Label de l'environnement d'image (qui doit se trouver dans la même page).
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="GLOSSAIRE">
<TITRE>Mot du glossaire</TITRE>
<DOCUMENTATION>
Renvoi au glossaire des mots importants ou jargonneux,
et éventuelle proposition de définition pour le glossaire.
Les attributs définissent le mot affiché et le mot de référence,
et le contenu de l'élément correspond à sa définition.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="mot">
<TITRE>mot</TITRE>
<DOCUMENTATION>
Mot affiché.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="ref">
<TITRE>ref</TITRE>
<DOCUMENTATION>
Mot de référence dans le glossaire, s'il est différent du mot affiché.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="BIBLIOGRAPHIE">
<TITRE>Bibliographie</TITRE>
<DOCUMENTATION>
Liste de références bibliographiques et webographiques.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="titre">
<TITRE>titre</TITRE>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="REFOUVRAGE">
<TITRE>Réf. ouvrage</TITRE>
<DOCUMENTATION>
Référence bibliographique pour un ouvrage.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="titre">
<TITRE>titre</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="sous-titre">
<TITRE>sous-titre</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="premierAuteur">
<TITRE>premier auteur</TITRE>
<DOCUMENTATION>
Ecrire sous la forme "NOM, Prénom".
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="autresAuteurs">
<TITRE>autres auteurs</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="volume">
<TITRE>volume</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="lieu">
<TITRE>lieu</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="editeur">
<TITRE>éditeur</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="annee">
<TITRE>année</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="pagination">
<TITRE>pagination</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="collection">
<TITRE>collection</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="isbn">
<TITRE>ISBN</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="url">
<TITRE>URL</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="autre">
<TITRE>autre</TITRE>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="REFARTICLE">
<TITRE>Réf. article</TITRE>
<DOCUMENTATION>
Référence bibliographique pour un article de périodique.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="titreArticle">
<TITRE>titre de l'article</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="titrePeriodique">
<TITRE>titre du périodique</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="premierAuteur">
<TITRE>premier auteur</TITRE>
<DOCUMENTATION>
Ecrire sous la forme "NOM, Prénom".
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="autresAuteurs">
<TITRE>autres auteurs</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="annee">
<TITRE>année</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="volume">
<TITRE>volume</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="numero">
<TITRE>numéro</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="pagination">
<TITRE>pagination</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="url">
<TITRE>URL</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="autre">
<TITRE>autre</TITRE>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="REFWEB">
<TITRE>Réf. web</TITRE>
<DOCUMENTATION>
Référence pour une page web.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="url">
<TITRE>URL</TITRE>
<DOCUMENTATION>
Adresse de la page web. Doit commencer par "http://".
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="auteurs">
<TITRE>auteurs</TITRE>
<DOCUMENTATION>
Auteurs ou collectivité.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="titre">
<TITRE>titre</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="titreSite">
<TITRE>titre du site</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="dateConsultation">
<TITRE>date de consultation</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="description">
<TITRE>description</TITRE>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="SYMBOLE">
<TITRE>Symbole</TITRE>
<DOCUMENTATION>
Lettre grecque ou petite image à incorporer dans le texte.
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="EQUATION">
<TITRE>Equation</TITRE>
<DOCUMENTATION>
Equation: texte de l'équation dans l'attribut texte (avec la syntaxe de Jaxe),
et image PNG encodée en base64 à l'intérieur de l'élément.
Plus tard l'image sera redirigée vers un fichier dont la référence
sera dans un attribut "image".
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="texte">
<TITRE>texte</TITRE>
<DOCUMENTATION>
Texte de l'équation, avec la syntaxe de Jaxe.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="CODE">
<TITRE>Code</TITRE>
<DOCUMENTATION>
Code informatique (généralement affiché avec une police dont les lettres ont la même taille)
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="EQUATEX">
<TITRE>Equation TeX</TITRE>
<DOCUMENTATION>
Equation TeX: texte de l'équation dans l'attribut texte avec la syntaxe TeX,
et image PNG encodée en base64 à l'intérieur de l'élément.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="texte">
<TITRE>texte</TITRE>
<DOCUMENTATION>
Texte de l'équation, avec la syntaxe TeX.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="CODE">
<TITRE>Code</TITRE>
<DOCUMENTATION>
Code informatique (généralement affiché avec une police dont les lettres ont la même taille)
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="COMMENTAIRE">
<TITRE>Commentaire</TITRE>
<DOCUMENTATION>
Commentaire de l'auteur, qui ne sera pas affiché dans le résultat final.
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="NBSP">
<TITRE>_</TITRE>
<DOCUMENTATION>
Espace insécable
</DOCUMENTATION>
</STRINGS_ELEMENT>

<STRINGS_ELEMENT element="SUP">
<TITRE>Exposant</TITRE>
<DOCUMENTATION>
Exposant
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="SUB">
<TITRE>Indice</TITRE>
<DOCUMENTATION>
Indice
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="EM">
<TITRE>Emphase</TITRE>
<DOCUMENTATION>
Emphase
</DOCUMENTATION>
</STRINGS_ELEMENT>

<STRINGS_EXPORT export="Site public">
<TITRE>Site public...</TITRE>
<DOCUMENTATION>
Génération du site sans les solutions des exercices d'évaluation, ni les commentaires des auteurs.
</DOCUMENTATION>
</STRINGS_EXPORT>
<STRINGS_EXPORT export="Site de production">
<TITRE>Site de production...</TITRE>
<DOCUMENTATION>
Génération du site avec les solutions des exercices d'évaluation et les commentaires des auteurs.
Produit aussi automatiquement des fichiers qui aident à corriger les mauvais liens.
</DOCUMENTATION>
</STRINGS_EXPORT>
</STRINGS>

<STRINGS langue="en">
<DESCRIPTION_CONFIG>Set of pages on a given subject</DESCRIPTION_CONFIG>

<STRINGS_MENU menu="Divisions">
<TITRE>Divisions</TITRE>
<STRINGS_MENU menu="PARAGRAPHE">
<TITRE>Paragraph</TITRE>
</STRINGS_MENU>
</STRINGS_MENU>
<STRINGS_MENU menu="Listes/Tableaux">
<TITRE>Lists/Tables</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="Figures">
<TITRE>Figures</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="Interactif">
<TITRE>Interactive</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="Autres">
<TITRE>Other</TITRE>
<STRINGS_MENU menu="NBSP">
<TITRE>Non-breaking space</TITRE>
</STRINGS_MENU>
</STRINGS_MENU>
<STRINGS_MENU menu="Style">
<TITRE>Style</TITRE>
</STRINGS_MENU>
<STRINGS_MENU menu="Normal">
<TITRE>Normal</TITRE>
</STRINGS_MENU>

<STRINGS_ELEMENT element="XPAGES">
<TITRE>XPAGES set</TITRE>
<DOCUMENTATION>
A set of pages on a given subject.
</DOCUMENTATION>
</STRINGS_ELEMENT>

<STRINGS_ELEMENT element="INFORMATIONS">
<TITRE>Informations</TITRE>
<DOCUMENTATION>
Informations about the document. The label and title are required.
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="LABEL">
<TITRE>Label</TITRE>
<DOCUMENTATION>
Identifier for this contribution
Can only contain lowercase letters from a to z, digits from 0 to 9, and the character "-".
The name of the XML file and the name of the folder must match the label.
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="TITRE">
<TITRE>Title</TITRE>
<DOCUMENTATION>
Title for the set of pages.
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="AUTEUR">
<TITRE>Author</TITRE>
<DOCUMENTATION>
An author
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="DATECRE">
<TITRE>Creation date</TITRE>
<DOCUMENTATION>
Date of creation, with the format DD/MM/YYYY
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="DATEMAJ">
<TITRE>Update date</TITRE>
<DOCUMENTATION>
Date of update, with the format DD/MM/YYYY
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="LOGO">
<TITRE>Logo</TITRE>
<DOCUMENTATION>
Image file for a logo to use in the site interface.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="fichier">
<TITRE>file</TITRE>
<DOCUMENTATION>
Path to the image file, relative to the XML file.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="lien">
<TITRE>link</TITRE>
<DOCUMENTATION>
Optional link to a web page to go to when the logo is clicked.
Must start with "http://".
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="INTERFACE">
<TITRE>Interface</TITRE>
<DOCUMENTATION>
Interface for the pages.
</DOCUMENTATION>
<TITRE_VALEUR valeur="azur">azure</TITRE_VALEUR>
<TITRE_VALEUR valeur="bois">wood</TITRE_VALEUR>
<TITRE_VALEUR valeur="etoiles">stars</TITRE_VALEUR>
<TITRE_VALEUR valeur="marbre">marble</TITRE_VALEUR>
<TITRE_VALEUR valeur="parchemin">parchment</TITRE_VALEUR>
<TITRE_VALEUR valeur="peinture">painting</TITRE_VALEUR>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="RUBRIQUES">
<TITRE>Menubar</TITRE>
<DOCUMENTATION>
Displays the top menubar or not. By default, uses the value of the parent element.
The root XPAGES element has the "yes" value by default.
</DOCUMENTATION>
<TITRE_VALEUR valeur="oui">yes</TITRE_VALEUR>
<TITRE_VALEUR valeur="non">no</TITRE_VALEUR>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="LANGUE">
<TITRE>Language</TITRE>
<DOCUMENTATION>
Code for the language to use in the user interface.
</DOCUMENTATION>
</STRINGS_ELEMENT>

<STRINGS_ELEMENT element="PAGE">
<TITRE>Page</TITRE>
<DOCUMENTATION>
A page. Ideally, it should fit a computer screen, and be about 1000 characters long.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="label">
<TITRE>label</TITRE>
<DOCUMENTATION>
Label of the page (hidden identifier used to create links to this page).
Should only contain lowercase letters from a to z and the character "-".
Use the "index" label for the website home page.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="titre">
<TITRE>title</TITRE>
<DOCUMENTATION>
Title for the page.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="role">
<TITRE>role</TITRE>
<DOCUMENTATION>
Semantic role (meaning) of the page.
</DOCUMENTATION>
<TITRE_VALEUR valeur="autre">other</TITRE_VALEUR>
<TITRE_VALEUR valeur="introduction">introduction</TITRE_VALEUR>
<TITRE_VALEUR valeur="conclusion">conclusion</TITRE_VALEUR>
<TITRE_VALEUR valeur="prerequis">prerequisites</TITRE_VALEUR>
<TITRE_VALEUR valeur="objectifs">objectives</TITRE_VALEUR>
<TITRE_VALEUR valeur="rappel">reminder</TITRE_VALEUR>
<TITRE_VALEUR valeur="definition">definition</TITRE_VALEUR>
<TITRE_VALEUR valeur="demonstration">demonstration</TITRE_VALEUR>
<TITRE_VALEUR valeur="exemple">example</TITRE_VALEUR>
<TITRE_VALEUR valeur="conseil">advise</TITRE_VALEUR>
<TITRE_VALEUR valeur="remarque">remark</TITRE_VALEUR>
<TITRE_VALEUR valeur="attention">warning</TITRE_VALEUR>
<TITRE_VALEUR valeur="complement">additional_information</TITRE_VALEUR>
<TITRE_VALEUR valeur="methode">method</TITRE_VALEUR>
<TITRE_VALEUR valeur="activite">activity</TITRE_VALEUR>
<TITRE_VALEUR valeur="bibliographie">bibliography</TITRE_VALEUR>
<TITRE_VALEUR valeur="citation">citation</TITRE_VALEUR>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="REFXPAGES">
<TITRE>Ref. XPAGES document</TITRE>
<DOCUMENTATION>
Reference to another set of pages, whose title will be displayed
in the table of contents for this set.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="label">
<TITRE>label</TITRE>
<DOCUMENTATION>
Label of the referenced XPAGES document.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="SECTION">
<TITRE>Section</TITRE>
<DOCUMENTATION>
A section inside a page, usually with a title.
The "role" attribute is used to give meaning to the section, for later XML transformations or a specific display.
The importance "high" is used to highlight the section from the rest of the text.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="titre">
<TITRE>title</TITRE>
<DOCUMENTATION>
Section title.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="role">
<TITRE>role</TITRE>
<DOCUMENTATION>
Semantic role (meaning) of the section.
</DOCUMENTATION>
<TITRE_VALEUR valeur="autre">other</TITRE_VALEUR>
<TITRE_VALEUR valeur="introduction">introduction</TITRE_VALEUR>
<TITRE_VALEUR valeur="conclusion">conclusion</TITRE_VALEUR>
<TITRE_VALEUR valeur="prerequis">prerequisites</TITRE_VALEUR>
<TITRE_VALEUR valeur="objectifs">objectives</TITRE_VALEUR>
<TITRE_VALEUR valeur="rappel">reminder</TITRE_VALEUR>
<TITRE_VALEUR valeur="definition">definition</TITRE_VALEUR>
<TITRE_VALEUR valeur="demonstration">demonstration</TITRE_VALEUR>
<TITRE_VALEUR valeur="exemple">example</TITRE_VALEUR>
<TITRE_VALEUR valeur="conseil">advise</TITRE_VALEUR>
<TITRE_VALEUR valeur="remarque">remark</TITRE_VALEUR>
<TITRE_VALEUR valeur="attention">warning</TITRE_VALEUR>
<TITRE_VALEUR valeur="complement">additional_information</TITRE_VALEUR>
<TITRE_VALEUR valeur="methode">method</TITRE_VALEUR>
<TITRE_VALEUR valeur="activite">activity</TITRE_VALEUR>
<TITRE_VALEUR valeur="bibliographie">bibliography</TITRE_VALEUR>
<TITRE_VALEUR valeur="citation">citation</TITRE_VALEUR>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="importance">
<TITRE>importance</TITRE>
<DOCUMENTATION>
Importance of the section. An important section will be more visible
in the generated website.
</DOCUMENTATION>
<TITRE_VALEUR valeur="normale">normal</TITRE_VALEUR>
<TITRE_VALEUR valeur="grande">high</TITRE_VALEUR>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="PARAGRAPHE">
<TITRE>P</TITRE>
<DOCUMENTATION>
A paragraph (a few lines without a line break).
</DOCUMENTATION>
</STRINGS_ELEMENT>

<STRINGS_ELEMENT element="TABLEAU">
<TITRE>Table</TITRE>
<DOCUMENTATION>
Table
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="titre">
<TITRE>title</TITRE>
<DOCUMENTATION>
Table title
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="TR">
<TITRE>Table row</TITRE>
<DOCUMENTATION>
Table row.
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="TD">
<TITRE>Table cell</TITRE>
<DOCUMENTATION>
Table cell.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="align">
<TITRE>align</TITRE>
<DOCUMENTATION>
Alignement for the text in the cell
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="colspan">
<TITRE>colspan</TITRE>
<DOCUMENTATION>
Horizontal cell gathering :
number of cells to regroup to the right of this one.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="rowspan">
<TITRE>rowspan</TITRE>
<DOCUMENTATION>
Vertical cell gathering :
number of cells to regroup to the bottom of this one.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="TH">
<TITRE>Table Header cell</TITRE>
<DOCUMENTATION>
Column header
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="align">
<TITRE>align</TITRE>
<DOCUMENTATION>
Alignement for the text in the cell
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="colspan">
<TITRE>colspan</TITRE>
<DOCUMENTATION>
Horizontal cell gathering :
number of cells to regroup to the right of this one.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="rowspan">
<TITRE>rowspan</TITRE>
<DOCUMENTATION>
Vertical cell gathering :
number of cells to regroup to the bottom of this one.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="LISTE">
<TITRE>List</TITRE>
<DOCUMENTATION>
Bulleted or ordered list
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="type">
<TITRE>type</TITRE>
<TITRE_VALEUR valeur="puces">bullets</TITRE_VALEUR>
<TITRE_VALEUR valeur="numéros">numbers</TITRE_VALEUR>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="ITEM">
<TITRE>List item</TITRE>
<DOCUMENTATION>
Element in a list.
</DOCUMENTATION>
</STRINGS_ELEMENT>

<STRINGS_ELEMENT element="ENVIMAGE">
<TITRE>Image environment</TITRE>
<DOCUMENTATION>
Environnement for images or animations.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="titre">
<TITRE>title</TITRE>
<DOCUMENTATION>
Title for the image or animation.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="localisation">
<TITRE>layout</TITRE>
<DOCUMENTATION>
Position for the image :
- text : within the text, with the original size
- page : in the page next to the text, reduced if necessary
- icon : as a small icon, next to the following paragraph
</DOCUMENTATION>
<TITRE_VALEUR valeur="texte">text</TITRE_VALEUR>
<TITRE_VALEUR valeur="page">page</TITRE_VALEUR>
<TITRE_VALEUR valeur="icône">icon</TITRE_VALEUR>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="FICHIER">
<TITRE>File</TITRE>
<DOCUMENTATION>
File for an image or animation to include in the text.
If the image or animation has to be followed by a legend,
it is necessary to used the IMAGEENV element.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="nom">
<TITRE>name</TITRE>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="FORMATS">
<TITRE>Formats</TITRE>
<DOCUMENTATION>
Can be used to specify several files, each with a different format, for the same media.
Only one file will be displayed, chosen according to the capabilities of the display environment.
This element is only useful with formats that are not readable everywhere, for instance for videos.
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="LEGENDE">
<TITRE>Legend</TITRE>
<DOCUMENTATION>
Legend for a table or a figure.
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="CREDIT">
<TITRE>Copyright info</TITRE>
<DOCUMENTATION>
Copyright information
</DOCUMENTATION>
</STRINGS_ELEMENT>

<STRINGS_ELEMENT element="APPLICATION">
<TITRE>Interactive application</TITRE>
<DOCUMENTATION>
Interactive application. Replaces APPLET and FLASH by generalizing the concept.
The type attribute specifies the application type (Java applet, Flash or HTML/Javascript).
The file attribute must be a .jar for Java applets, a .swf for Flash applications, and a .html for Javascript applications.
Java applets must have a "code" parameter with the class name.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="type">
<TITRE>type</TITRE>
<DOCUMENTATION>
Application type (Java applet, Flash or HTML/Javascript).
</DOCUMENTATION>
<TITRE_VALEUR valeur="applet">Java applet</TITRE_VALEUR>
<TITRE_VALEUR valeur="flash">Flash</TITRE_VALEUR>
<TITRE_VALEUR valeur="html">HTML/Javascript</TITRE_VALEUR>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="titre">
<TITRE>title</TITRE>
<DOCUMENTATION>
Application title
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="fichier">
<TITRE>file</TITRE>
<DOCUMENTATION>
Application file.
Must be a .jar for Java applets, a .swf for Flash applications, and a .html for Javascript applications.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="contribution">
<TITRE>contribution</TITRE>
<DOCUMENTATION>
Contribution of the application file.
Can be used to avoid copying many times an application used in several contributions.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="largeur">
<TITRE>width</TITRE>
<DOCUMENTATION>
Width for the application area, in pixels.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="hauteur">
<TITRE>height</TITRE>
<DOCUMENTATION>
Height for the application area, in pixels.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="localisation">
<TITRE>layout</TITRE>
<DOCUMENTATION>
Position for the application :
- text : within the text
- page : in the page next to the text
- icon : available with a small icon, next to the following paragraph
</DOCUMENTATION>
<TITRE_VALEUR valeur="texte">text</TITRE_VALEUR>
<TITRE_VALEUR valeur="page">page</TITRE_VALEUR>
<TITRE_VALEUR valeur="icône">icon</TITRE_VALEUR>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="PARAM">
<TITRE>Parameter</TITRE>
<DOCUMENTATION>
Parameter for a Java applet of a Flash object.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="nom">
<TITRE>name</TITRE>
<DOCUMENTATION>
Parameter name.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="valeur">
<TITRE>value</TITRE>
<DOCUMENTATION>
Parameter value.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>

<STRINGS_ELEMENT element="EXERCICE">
<TITRE>Exercise</TITRE>
<DOCUMENTATION>
Exercise with questions and answers
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="titre">
<TITRE>title</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="type">
<TITRE>type</TITRE>
<TITRE_VALEUR valeur="évaluation">evaluation</TITRE_VALEUR>
<TITRE_VALEUR valeur="auto-évaluation">self-assessment</TITRE_VALEUR>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="DIFFICULTE">
<TITRE>Difficulty</TITRE>
<DOCUMENTATION>
gives an idea of the difficulty :
* : easy, direct application of the lecture
** : medium
*** : hard
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="TEMPS">
<TITRE>Time</TITRE>
<DOCUMENTATION>
Indicative time to solve the exercise
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="QUESTION">
<TITRE>Question</TITRE>
<DOCUMENTATION>
Exercise question, with the terms, optional helps, a solution, and optional points.
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="ENONCE">
<TITRE>Terms</TITRE>
<DOCUMENTATION>
Terms of an exercise or a question
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="AIDE">
<TITRE>Help</TITRE>
<DOCUMENTATION>
Help for a question
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="SOLUTION">
<TITRE>Solution</TITRE>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="POINTS">
<TITRE>Points</TITRE>
<DOCUMENTATION>
Awarded points if the answer is correct
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="QCM">
<TITRE>Quiz</TITRE>
<DOCUMENTATION>
Quiz with comments displayed depending on the user's answer
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="titre">
<TITRE>title</TITRE>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="QUESTIONQCM">
<TITRE>Quiz question</TITRE>
<DOCUMENTATION>
Quiz question, with the terms, optional helps, and possible answers.
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="ENONCEQCM">
<TITRE>Question terms</TITRE>
<DOCUMENTATION>
Quiz question terms (short text)
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="REPONSEQCM">
<TITRE>Quiz answer</TITRE>
<DOCUMENTATION>
Possible answer for a quiz. Contains the text of the answer and a comment to display after a choice. The "correct" attribute tells if the answer is right or wrong.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="bonne">
<TITRE>correct</TITRE>
<TITRE_VALEUR valeur="non">no</TITRE_VALEUR>
<TITRE_VALEUR valeur="oui">yes</TITRE_VALEUR>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="TEXTEREP">
<TITRE>Answer text</TITRE>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="COMMENTAIREREP">
<TITRE>Answer comment</TITRE>
<DOCUMENTATION>
Comment to display after a choice (especially if the answer in wrong).
</DOCUMENTATION>
</STRINGS_ELEMENT>

<STRINGS_ELEMENT element="LIENPAGE">
<TITRE>Link to internal page</TITRE>
<DOCUMENTATION>
Link to a page in the same contribution (if "contribution" is empty),
or link to a page in another contribution (with the "contribution" attribute specifying the contribution label).
Must contain text or a file upon which the link will be displayed.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="contribution">
<TITRE>contribution</TITRE>
<DOCUMENTATION>
The contribution label. Empty for a link to the same contribution.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="page">
<TITRE>page</TITRE>
<DOCUMENTATION>
The page label. Empty to create a link to the first page in a contribution.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="LIENWEB">
<TITRE>External link</TITRE>
<DOCUMENTATION>
External link (with a URL strating with "http://").
Must contain text or a file upon which the link will be displayed.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="url">
<TITRE>url</TITRE>
<DOCUMENTATION>
Address for the web page. Must start with "http://".
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="LIENIMAGE">
<TITRE>Image link</TITRE>
<DOCUMENTATION>
Link to an image environnement in the same page.
Must contain text or a file upon which the link will be displayed.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="url">
<TITRE>label</TITRE>
<DOCUMENTATION>
Label for the image environnement (which must be in the same page).
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="GLOSSAIRE">
<TITRE>Glossary word</TITRE>
<DOCUMENTATION>
Refers to the glossary, and can suggest a definition for the glossary.
The attributes define the displayed word and the reference word,
while the element content is the definition.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="mot">
<TITRE>word</TITRE>
<DOCUMENTATION>
Displayed word.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="ref">
<TITRE>ref</TITRE>
<DOCUMENTATION>
Reference word in the glossary, if different from the displayed word.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="BIBLIOGRAPHIE">
<TITRE>Bibliography</TITRE>
<DOCUMENTATION>
List of bibliographic and webographic references.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="titre">
<TITRE>title</TITRE>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="REFOUVRAGE">
<TITRE>Ref. monograph</TITRE>
<DOCUMENTATION>
Bibliographic reference for a monograph.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="titre">
<TITRE>title</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="sous-titre">
<TITRE>subtitle</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="premierAuteur">
<TITRE>first author</TITRE>
<DOCUMENTATION>
Write with the syntax "LASTNAME, Firstname".
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="autresAuteurs">
<TITRE>other authors</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="volume">
<TITRE>volume</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="lieu">
<TITRE>location</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="editeur">
<TITRE>editor</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="annee">
<TITRE>year</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="pagination">
<TITRE>pagination</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="collection">
<TITRE>collection</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="isbn">
<TITRE>isbn number</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="url">
<TITRE>URL</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="autre">
<TITRE>other</TITRE>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="REFARTICLE">
<TITRE>Ref. article</TITRE>
<DOCUMENTATION>
Bibliographic reference for an article.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="titreArticle">
<TITRE>article title</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="titrePeriodique">
<TITRE>serial title</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="premierAuteur">
<TITRE>first author</TITRE>
<DOCUMENTATION>
Write with the syntax "LASTNAME, Firstname".
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="autresAuteurs">
<TITRE>other authors</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="annee">
<TITRE>year</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="volume">
<TITRE>volume</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="numero">
<TITRE>number</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="pagination">
<TITRE>pagination</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="url">
<TITRE>URL</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="autre">
<TITRE>other</TITRE>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="REFWEB">
<TITRE>Ref. web</TITRE>
<DOCUMENTATION>
Reference for a web page.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="url">
<TITRE>URL</TITRE>
<DOCUMENTATION>
Web page URL. Must start with "http://".
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="auteurs">
<TITRE>authors</TITRE>
<DOCUMENTATION>
Authors or collectivity.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="titre">
<TITRE>title</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="titreSite">
<TITRE>Website title</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="dateConsultation">
<TITRE>consultation date</TITRE>
</STRINGS_ATTRIBUT>
<STRINGS_ATTRIBUT attribut="description">
<TITRE>description</TITRE>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="SYMBOLE">
<TITRE>Symbol</TITRE>
<DOCUMENTATION>
Greek letter or small image to insert in the text.
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="EQUATION">
<TITRE>Equation</TITRE>
<DOCUMENTATION>
Equation : text for the equation in the "text" attribute (with Jaxe's syntax),
and PNG image encoded in base64 inside the XML document.
Later, the image will be redirected towards a file whose reference will be
in an "image" attribute.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="texte">
<TITRE>text</TITRE>
<DOCUMENTATION>
Text for the equation, with Jaxe's syntax.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="EQUATEX">
<TITRE>TeX Equation</TITRE>
<DOCUMENTATION>
TeX Equation : text for the equation in the "text" attribute (with TeX syntax),
and PNG image encoded in base64 inside the XML document.
</DOCUMENTATION>
<STRINGS_ATTRIBUT attribut="texte">
<TITRE>text</TITRE>
<DOCUMENTATION>
Text for the equation, using TeX syntax.
</DOCUMENTATION>
</STRINGS_ATTRIBUT>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="CODE">
<TITRE>Code</TITRE>
<DOCUMENTATION>
Computer code (usually displayed with a monospaced font).
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="COMMENTAIRE">
<TITRE>Comment</TITRE>
<DOCUMENTATION>
Author comment, that will not be displayed in the final result.
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="NBSP">
<TITRE>_</TITRE>
<DOCUMENTATION>
Non-breaking space.
</DOCUMENTATION>
</STRINGS_ELEMENT>

<STRINGS_ELEMENT element="SUP">
<TITRE>Superscript</TITRE>
<DOCUMENTATION>
Superscript
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="SUB">
<TITRE>Subscript</TITRE>
<DOCUMENTATION>
Subscript
</DOCUMENTATION>
</STRINGS_ELEMENT>
<STRINGS_ELEMENT element="EM">
<TITRE>Emphasis</TITRE>
<DOCUMENTATION>
Emphasis
</DOCUMENTATION>
</STRINGS_ELEMENT>

<STRINGS_EXPORT export="Site public">
<TITRE>Public website...</TITRE>
<DOCUMENTATION>
Generates the website without the solutions for evaluation exercises, and without the author comments.
</DOCUMENTATION>
</STRINGS_EXPORT>
<STRINGS_EXPORT export="Site de production">
<TITRE>Production website...</TITRE>
<DOCUMENTATION>
Generates the website with the solutions for evaluation exercises and the author comments.
Also creates automatically some files to help fix broken links.
</DOCUMENTATION>
</STRINGS_EXPORT>
</STRINGS>
</CONFIG_JAXE>

Index: modules/damieng/graphical_editor/daxe/web/config/xhtml1-strict.xsd
+++ modules/damieng/graphical_editor/daxe/web/config/xhtml1-strict.xsd
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema version="1.0" xml:lang="en"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    targetNamespace="http://www.w3.org/1999/xhtml"
    xmlns="http://www.w3.org/1999/xhtml"
    xmlns:xml="http://www.w3.org/XML/1998/namespace"
    elementFormDefault="qualified">

  <xs:annotation>
    <xs:documentation>
    XHTML 1.0 (Second Edition) Strict in XML Schema

    This is the same as HTML 4 Strict except for
    changes due to the differences between XML and SGML.

    Namespace = http://www.w3.org/1999/xhtml

    For further information, see: http://www.w3.org/TR/xhtml1

    Copyright (c) 1998-2002 W3C (MIT, INRIA, Keio),
    All Rights Reserved. 

    The DTD version is identified by the PUBLIC and SYSTEM identifiers:

    PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"

    $Id: xhtml1-strict.xsd,v 1.1 2015/04/17 15:35:05 damieng Exp $
    </xs:documentation>
  </xs:annotation>

<!--
  <xs:import namespace="http://www.w3.org/XML/1998/namespace"
      schemaLocation="http://www.w3.org/2001/xml.xsd"/>
-->
  <xs:import namespace="http://www.w3.org/XML/1998/namespace"
      schemaLocation="xml.xsd"/>

  <xs:annotation>
    <xs:documentation>
    ================ Character mnemonic entities =========================

    XHTML entity sets are identified by the PUBLIC and SYSTEM identifiers:
  
    PUBLIC "-//W3C//ENTITIES Latin 1 for XHTML//EN"
    SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent"

    PUBLIC "-//W3C//ENTITIES Special for XHTML//EN"
    SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml-special.ent"

    PUBLIC "-//W3C//ENTITIES Symbols for XHTML//EN"
    SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent"
    </xs:documentation>
  </xs:annotation>

  <xs:annotation>
    <xs:documentation>
    ================== Imported Names ====================================
    </xs:documentation>
  </xs:annotation>

  <xs:simpleType name="ContentType">
    <xs:annotation>
      <xs:documentation>
      media type, as per [RFC2045]
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string"/>
  </xs:simpleType>

  <xs:simpleType name="ContentTypes">
    <xs:annotation>
      <xs:documentation>
      comma-separated list of media types, as per [RFC2045]
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string"/>
  </xs:simpleType>

  <xs:simpleType name="Charset">
    <xs:annotation>
      <xs:documentation>
      a character encoding, as per [RFC2045]
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string"/>
  </xs:simpleType>

  <xs:simpleType name="Charsets">
    <xs:annotation>
      <xs:documentation>
      a space separated list of character encodings, as per [RFC2045]
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string"/>
  </xs:simpleType>

  <xs:simpleType name="LanguageCode">
    <xs:annotation>
      <xs:documentation>
      a language code, as per [RFC3066]
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:language"/>
  </xs:simpleType>

  <xs:simpleType name="Character">
    <xs:annotation>
      <xs:documentation>
      a single character, as per section 2.2 of [XML]
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string">
      <xs:length value="1" fixed="true"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="Number">
    <xs:annotation>
      <xs:documentation>
      one or more digits
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:nonNegativeInteger">
      <xs:pattern value="[0-9]+"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="tabindexNumber">
    <xs:annotation>
      <xs:documentation>
      tabindex attribute specifies the position of the current element
      in the tabbing order for the current document. This value must be
      a number between 0 and 32767. User agents should ignore leading zeros. 
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="Number">
      <xs:minInclusive value="0"/>
      <xs:maxInclusive value="32767"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="LinkTypes">
    <xs:annotation>
      <xs:documentation>
      space-separated list of link types
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:NMTOKENS"/>
  </xs:simpleType>

  <xs:simpleType name="MediaDesc">
    <xs:annotation>
      <xs:documentation>
      single or comma-separated list of media descriptors
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string">
      <xs:pattern value="[^,]+(,\s*[^,]+)*"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="URI">
    <xs:annotation>
      <xs:documentation>
      a Uniform Resource Identifier, see [RFC2396]
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:anyURI"/>
  </xs:simpleType>

  <xs:simpleType name="UriList">
    <xs:annotation>
      <xs:documentation>
      a space separated list of Uniform Resource Identifiers
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string"/>
  </xs:simpleType>

  <xs:simpleType name="Datetime">
    <xs:annotation>
      <xs:documentation>
      date and time information. ISO date format
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:dateTime"/>
  </xs:simpleType>

  <xs:simpleType name="Script">
    <xs:annotation>
      <xs:documentation>
      script expression
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string"/>
  </xs:simpleType>

  <xs:simpleType name="StyleSheet">
    <xs:annotation>
      <xs:documentation>
      style sheet data
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string"/>
  </xs:simpleType>

  <xs:simpleType name="Text">
    <xs:annotation>
      <xs:documentation>
      used for titles etc.
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string"/>
  </xs:simpleType>

  <xs:simpleType name="Length">
    <xs:annotation>
      <xs:documentation>
      nn for pixels or nn% for percentage length
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string">
      <xs:pattern value="[\-+]?(\d+|\d+(\.\d+)?%)"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="MultiLength">
    <xs:annotation>
      <xs:documentation>
      pixel, percentage, or relative
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string">
      <xs:pattern value="[\-+]?(\d+|\d+(\.\d+)?%)|[1-9]?(\d+)?\*"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="Pixels">
    <xs:annotation>
      <xs:documentation>
      integer representing length in pixels
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:nonNegativeInteger"/>
  </xs:simpleType>

  <xs:annotation>
    <xs:documentation>
    these are used for image maps
    </xs:documentation>
  </xs:annotation>

  <xs:simpleType name="Shape">
    <xs:restriction base="xs:token">
      <xs:enumeration value="rect"/>
      <xs:enumeration value="circle"/>
      <xs:enumeration value="poly"/>
      <xs:enumeration value="default"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="Coords">
    <xs:annotation>
      <xs:documentation>
      comma separated list of lengths
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string">
      <xs:pattern
          value="[\-+]?(\d+|\d+(\.\d+)?%)(,\s*[\-+]?(\d+|\d+(\.\d+)?%))*"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:annotation>
    <xs:documentation>
    =================== Generic Attributes ===============================
    </xs:documentation>
  </xs:annotation>

  <xs:attributeGroup name="coreattrs">
    <xs:annotation>
      <xs:documentation>
      core attributes common to most elements
      id       document-wide unique id
      class    space separated list of classes
      style    associated style info
      title    advisory title/amplification
      </xs:documentation>
    </xs:annotation>
    <xs:attribute name="id" type="xs:ID"/>
    <xs:attribute name="class" type="xs:NMTOKENS"/>
    <xs:attribute name="style" type="StyleSheet"/>
    <xs:attribute name="title" type="Text"/>
  </xs:attributeGroup>

  <xs:attributeGroup name="i18n">
    <xs:annotation>
      <xs:documentation>
      internationalization attributes
      lang        language code (backwards compatible)
      xml:lang    language code (as per XML 1.0 spec)
      dir         direction for weak/neutral text
      </xs:documentation>
    </xs:annotation>
    <xs:attribute name="lang" type="LanguageCode"/>
    <xs:attribute ref="xml:lang"/>
    <xs:attribute name="dir">
      <xs:simpleType>
        <xs:restriction base="xs:token">
          <xs:enumeration value="ltr"/>
          <xs:enumeration value="rtl"/>
        </xs:restriction>
      </xs:simpleType>
    </xs:attribute>
  </xs:attributeGroup>

  <xs:attributeGroup name="events">
    <xs:annotation>
      <xs:documentation>
      attributes for common UI events
      onclick     a pointer button was clicked
      ondblclick  a pointer button was double clicked
      onmousedown a pointer button was pressed down
      onmouseup   a pointer button was released
      onmousemove a pointer was moved onto the element
      onmouseout  a pointer was moved away from the element
      onkeypress  a key was pressed and released
      onkeydown   a key was pressed down
      onkeyup     a key was released
      </xs:documentation>
    </xs:annotation>
    <xs:attribute name="onclick" type="Script"/>
    <xs:attribute name="ondblclick" type="Script"/>
    <xs:attribute name="onmousedown" type="Script"/>
    <xs:attribute name="onmouseup" type="Script"/>
    <xs:attribute name="onmouseover" type="Script"/>
    <xs:attribute name="onmousemove" type="Script"/>
    <xs:attribute name="onmouseout" type="Script"/>
    <xs:attribute name="onkeypress" type="Script"/>
    <xs:attribute name="onkeydown" type="Script"/>
    <xs:attribute name="onkeyup" type="Script"/>
  </xs:attributeGroup>

  <xs:attributeGroup name="focus">
    <xs:annotation>
      <xs:documentation>
      attributes for elements that can get the focus
      accesskey   accessibility key character
      tabindex    position in tabbing order
      onfocus     the element got the focus
      onblur      the element lost the focus
      </xs:documentation>
    </xs:annotation>
    <xs:attribute name="accesskey" type="Character"/>
    <xs:attribute name="tabindex" type="tabindexNumber"/>
    <xs:attribute name="onfocus" type="Script"/>
    <xs:attribute name="onblur" type="Script"/>
  </xs:attributeGroup>

  <xs:attributeGroup name="attrs">
    <xs:attributeGroup ref="coreattrs"/>
    <xs:attributeGroup ref="i18n"/>
    <xs:attributeGroup ref="events"/>
  </xs:attributeGroup>

  <xs:annotation>
    <xs:documentation>
    =================== Text Elements ====================================
    </xs:documentation>
  </xs:annotation>

  <xs:group name="special.pre">
    <xs:choice>
      <xs:element ref="br"/>
      <xs:element ref="span"/>
      <xs:element ref="bdo"/>
      <xs:element ref="map"/>
    </xs:choice>
  </xs:group>

  <xs:group name="special">
    <xs:choice>
      <xs:group ref="special.pre"/>
      <xs:element ref="object"/>
      <xs:element ref="img"/>
    </xs:choice>
  </xs:group>

  <xs:group name="fontstyle">
    <xs:choice>
      <xs:element ref="tt"/>
      <xs:element ref="i"/>
      <xs:element ref="b"/>
      <xs:element ref="big"/>
      <xs:element ref="small"/>
    </xs:choice>
  </xs:group>

  <xs:group name="phrase">
    <xs:choice>
      <xs:element ref="em"/>
      <xs:element ref="strong"/>
      <xs:element ref="dfn"/>
      <xs:element ref="code"/>
      <xs:element ref="q"/>
      <xs:element ref="samp"/>
      <xs:element ref="kbd"/>
      <xs:element ref="var"/>
      <xs:element ref="cite"/>
      <xs:element ref="abbr"/>
      <xs:element ref="acronym"/>
      <xs:element ref="sub"/>
      <xs:element ref="sup"/>
    </xs:choice>
  </xs:group>

  <xs:group name="inline.forms">
    <xs:choice>
      <xs:element ref="input"/>
      <xs:element ref="select"/>
      <xs:element ref="textarea"/>
      <xs:element ref="label"/>
      <xs:element ref="button"/>
    </xs:choice>
  </xs:group>

  <xs:group name="misc.inline">
    <xs:choice>
      <xs:element ref="ins"/>
      <xs:element ref="del"/>
      <xs:element ref="script"/>
    </xs:choice>
  </xs:group>

  <xs:group name="misc">
    <xs:annotation>
      <xs:documentation>
      these can only occur at block level
      </xs:documentation>
    </xs:annotation>
    <xs:choice>
      <xs:element ref="noscript"/>
      <xs:group ref="misc.inline"/>
    </xs:choice>
  </xs:group>

  <xs:group name="inline">
    <xs:choice>
      <xs:element ref="a"/>
      <xs:group ref="special"/>
      <xs:group ref="fontstyle"/>
      <xs:group ref="phrase"/>
      <xs:group ref="inline.forms"/>
    </xs:choice>
  </xs:group>

  <xs:complexType name="Inline" mixed="true">
    <xs:annotation>
      <xs:documentation>
      "Inline" covers inline or "text-level" elements
      </xs:documentation>
    </xs:annotation>
    <xs:choice minOccurs="0" maxOccurs="unbounded">
      <xs:group ref="inline"/>
      <xs:group ref="misc.inline"/>
    </xs:choice>
  </xs:complexType>

  <xs:annotation>
    <xs:documentation>
    ================== Block level elements ==============================
    </xs:documentation>
  </xs:annotation>

  <xs:group name="heading">
    <xs:choice>
      <xs:element ref="h1"/>
      <xs:element ref="h2"/>
      <xs:element ref="h3"/>
      <xs:element ref="h4"/>
      <xs:element ref="h5"/>
      <xs:element ref="h6"/>
    </xs:choice>
  </xs:group>

  <xs:group name="lists">
    <xs:choice>
      <xs:element ref="ul"/>
      <xs:element ref="ol"/>
      <xs:element ref="dl"/>
    </xs:choice>
  </xs:group>

  <xs:group name="blocktext">
    <xs:choice>
      <xs:element ref="pre"/>
      <xs:element ref="hr"/>
      <xs:element ref="blockquote"/>
      <xs:element ref="address"/>
    </xs:choice>
  </xs:group>

  <xs:group name="block">
    <xs:choice>
      <xs:element ref="p"/>
      <xs:group ref="heading"/>
      <xs:element ref="div"/>
      <xs:group ref="lists"/>
      <xs:group ref="blocktext"/>
      <xs:element ref="fieldset"/>
      <xs:element ref="table"/>
    </xs:choice>
  </xs:group>

  <xs:complexType name="Block">
    <xs:choice minOccurs="0" maxOccurs="unbounded">
      <xs:group ref="block"/>
      <xs:element ref="form"/>
      <xs:group ref="misc"/>
    </xs:choice>
  </xs:complexType>

  <xs:complexType name="Flow" mixed="true">
    <xs:annotation>
      <xs:documentation>
      "Flow" mixes block and inline and is used for list items etc.
      </xs:documentation>
    </xs:annotation>
    <xs:choice minOccurs="0" maxOccurs="unbounded">
      <xs:group ref="block"/>
      <xs:element ref="form"/>
      <xs:group ref="inline"/>
      <xs:group ref="misc"/>
    </xs:choice>
  </xs:complexType>

  <xs:annotation>
    <xs:documentation>
    ================== Content models for exclusions =====================
    </xs:documentation>
  </xs:annotation>

  <xs:complexType name="a.content" mixed="true">
    <xs:annotation>
      <xs:documentation>
      a elements use "Inline" excluding a
      </xs:documentation>
    </xs:annotation>
    <xs:choice minOccurs="0" maxOccurs="unbounded">
      <xs:group ref="special"/>
      <xs:group ref="fontstyle"/>
      <xs:group ref="phrase"/>
      <xs:group ref="inline.forms"/>
      <xs:group ref="misc.inline"/>
    </xs:choice>
  </xs:complexType>

  <xs:complexType name="pre.content" mixed="true">
    <xs:annotation>
      <xs:documentation>
      pre uses "Inline" excluding big, small, sup or sup
      </xs:documentation>
    </xs:annotation>
    <xs:choice minOccurs="0" maxOccurs="unbounded">
      <xs:element ref="a"/>
      <xs:group ref="fontstyle"/>
      <xs:group ref="phrase"/>
      <xs:group ref="special.pre"/>
      <xs:group ref="misc.inline"/>
      <xs:group ref="inline.forms"/>
    </xs:choice>
  </xs:complexType>

  <xs:complexType name="form.content">
    <xs:annotation>
      <xs:documentation>
      form uses "Block" excluding form
      </xs:documentation>
    </xs:annotation>
    <xs:choice minOccurs="0" maxOccurs="unbounded">
      <xs:group ref="block"/>
      <xs:group ref="misc"/>
    </xs:choice>
  </xs:complexType>

  <xs:complexType name="button.content" mixed="true">
    <xs:annotation>
      <xs:documentation>
      button uses "Flow" but excludes a, form and form controls
      </xs:documentation>
    </xs:annotation>
    <xs:choice minOccurs="0" maxOccurs="unbounded">
      <xs:element ref="p"/>
      <xs:group ref="heading"/>
      <xs:element ref="div"/>
      <xs:group ref="lists"/>
      <xs:group ref="blocktext"/>
      <xs:element ref="table"/>
      <xs:group ref="special"/>
      <xs:group ref="fontstyle"/>
      <xs:group ref="phrase"/>
      <xs:group ref="misc"/>
    </xs:choice>
  </xs:complexType>

  <xs:annotation>
    <xs:documentation>
    ================ Document Structure ==================================
    </xs:documentation>
  </xs:annotation>

  <xs:element name="html">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="head"/>
        <xs:element ref="body"/>
      </xs:sequence>
      <xs:attributeGroup ref="i18n"/>
      <xs:attribute name="id" type="xs:ID"/>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    ================ Document Head =======================================
    </xs:documentation>
  </xs:annotation>

  <xs:group name="head.misc">
    <xs:sequence>
      <xs:choice minOccurs="0" maxOccurs="unbounded">
        <xs:element ref="script"/>
        <xs:element ref="style"/>
        <xs:element ref="meta"/>
        <xs:element ref="link"/>
        <xs:element ref="object"/>
      </xs:choice>
    </xs:sequence>
  </xs:group>

  <xs:element name="head">
    <xs:annotation>
      <xs:documentation>
      content model is "head.misc" combined with a single
      title and an optional base element in any order
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:sequence>
        <xs:group ref="head.misc"/>
        <xs:choice>
          <xs:sequence>
            <xs:element ref="title"/>
            <xs:group ref="head.misc"/>
            <xs:sequence minOccurs="0">
              <xs:element ref="base"/>
              <xs:group ref="head.misc"/>
            </xs:sequence>
          </xs:sequence>
          <xs:sequence>
            <xs:element ref="base"/>
            <xs:group ref="head.misc"/>
            <xs:element ref="title"/>
            <xs:group ref="head.misc"/>
          </xs:sequence>
        </xs:choice>
      </xs:sequence>
      <xs:attributeGroup ref="i18n"/>
      <xs:attribute name="id" type="xs:ID"/>
      <xs:attribute name="profile" type="URI"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="title">
    <xs:annotation>
      <xs:documentation>
      The title element is not considered part of the flow of text.
      It should be displayed, for example as the page header or
      window title. Exactly one title is required per document.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:attributeGroup ref="i18n"/>
      <xs:attribute name="id" type="xs:ID"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="base">
    <xs:annotation>
      <xs:documentation>
      document base URI
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="href" use="required" type="URI"/>
      <xs:attribute name="id" type="xs:ID"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="meta">
    <xs:annotation>
      <xs:documentation>
      generic metainformation
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attributeGroup ref="i18n"/>
      <xs:attribute name="id" type="xs:ID"/>
      <xs:attribute name="http-equiv"/>
      <xs:attribute name="name"/>
      <xs:attribute name="content" use="required"/>
      <xs:attribute name="scheme"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="link">
    <xs:annotation>
      <xs:documentation>
      Relationship values can be used in principle:

      a) for document specific toolbars/menus when used
         with the link element in document head e.g.
           start, contents, previous, next, index, end, help
      b) to link to a separate style sheet (rel="stylesheet")
      c) to make a link to a script (rel="script")
      d) by stylesheets to control how collections of
         html nodes are rendered into printed documents
      e) to make a link to a printable version of this document
         e.g. a PostScript or PDF version (rel="alternate" media="print")
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attributeGroup ref="attrs"/>
      <xs:attribute name="charset" type="Charset"/>
      <xs:attribute name="href" type="URI"/>
      <xs:attribute name="hreflang" type="LanguageCode"/>
      <xs:attribute name="type" type="ContentType"/>
      <xs:attribute name="rel" type="LinkTypes"/>
      <xs:attribute name="rev" type="LinkTypes"/>
      <xs:attribute name="media" type="MediaDesc"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="style">
    <xs:annotation>
      <xs:documentation>
      style info, which may include CDATA sections
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:attributeGroup ref="i18n"/>
      <xs:attribute name="id" type="xs:ID"/>
      <xs:attribute name="type" use="required" type="ContentType"/>
      <xs:attribute name="media" type="MediaDesc"/>
      <xs:attribute name="title" type="Text"/>
      <xs:attribute ref="xml:space" fixed="preserve"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="script">
    <xs:annotation>
      <xs:documentation>
      script statements, which may include CDATA sections
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:attribute name="id" type="xs:ID"/>
      <xs:attribute name="charset" type="Charset"/>
      <xs:attribute name="type" use="required" type="ContentType"/>
      <xs:attribute name="src" type="URI"/>
      <xs:attribute name="defer">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="defer"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute ref="xml:space" fixed="preserve"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="noscript">
    <xs:annotation>
      <xs:documentation>
      alternate content container for non script-based rendering
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:complexContent>
        <xs:extension base="Block">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    =================== Document Body ====================================
    </xs:documentation>
  </xs:annotation>

  <xs:element name="body">
    <xs:complexType>
      <xs:complexContent>
        <xs:extension base="Block">
          <xs:attributeGroup ref="attrs"/>
          <xs:attribute name="onload" type="Script"/>
          <xs:attribute name="onunload" type="Script"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="div">
    <xs:annotation>
      <xs:documentation>
      generic language/style container      
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Flow">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    =================== Paragraphs =======================================
    </xs:documentation>
  </xs:annotation>

  <xs:element name="p">
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    =================== Headings =========================================

    There are six levels of headings from h1 (the most important)
    to h6 (the least important).
    </xs:documentation>
  </xs:annotation>

  <xs:element name="h1">
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="h2">
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="h3">
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="h4">
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="h5">
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="h6">
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    =================== Lists ============================================
    </xs:documentation>
  </xs:annotation>

  <xs:element name="ul">
    <xs:annotation>
      <xs:documentation>
      Unordered list
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" ref="li"/>
      </xs:sequence>
      <xs:attributeGroup ref="attrs"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="ol">
    <xs:annotation>
      <xs:documentation>
      Ordered (numbered) list
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" ref="li"/>
      </xs:sequence>
      <xs:attributeGroup ref="attrs"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="li">
    <xs:annotation>
      <xs:documentation>
      list item
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Flow">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    definition lists - dt for term, dd for its definition
    </xs:documentation>
  </xs:annotation>

  <xs:element name="dl">
    <xs:complexType>
      <xs:choice maxOccurs="unbounded">
        <xs:element ref="dt"/>
        <xs:element ref="dd"/>
      </xs:choice>
      <xs:attributeGroup ref="attrs"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="dt">
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="dd">
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Flow">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    =================== Address ==========================================
    </xs:documentation>
  </xs:annotation>

  <xs:element name="address">
    <xs:annotation>
      <xs:documentation>
      information on author
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    =================== Horizontal Rule ==================================
    </xs:documentation>
  </xs:annotation>

  <xs:element name="hr">
    <xs:complexType>
      <xs:attributeGroup ref="attrs"/>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    =================== Preformatted Text ================================
    </xs:documentation>
  </xs:annotation>

  <xs:element name="pre">
    <xs:annotation>
      <xs:documentation>
      content is "Inline" excluding "img|object|big|small|sub|sup"
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="pre.content">
           <xs:attributeGroup ref="attrs"/>
           <xs:attribute ref="xml:space" fixed="preserve"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    =================== Block-like Quotes ================================
    </xs:documentation>
  </xs:annotation>

  <xs:element name="blockquote">
    <xs:complexType>
      <xs:complexContent>
        <xs:extension base="Block">
          <xs:attributeGroup ref="attrs"/>
          <xs:attribute name="cite" type="URI"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    =================== Inserted/Deleted Text ============================

    ins/del are allowed in block and inline content, but its
    inappropriate to include block content within an ins element
    occurring in inline content.
    </xs:documentation>
  </xs:annotation>

  <xs:element name="ins">
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Flow">
          <xs:attributeGroup ref="attrs"/>
          <xs:attribute name="cite" type="URI"/>
          <xs:attribute name="datetime" type="Datetime"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="del">
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Flow">
          <xs:attributeGroup ref="attrs"/>
          <xs:attribute name="cite" type="URI"/>
          <xs:attribute name="datetime" type="Datetime"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    ================== The Anchor Element ================================
    </xs:documentation>
  </xs:annotation>

  <xs:element name="a">
    <xs:annotation>
      <xs:documentation>
      content is "Inline" except that anchors shouldn't be nested
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="a.content">
          <xs:attributeGroup ref="attrs"/>
          <xs:attributeGroup ref="focus"/>
          <xs:attribute name="charset" type="Charset"/>
          <xs:attribute name="type" type="ContentType"/>
          <xs:attribute name="name" type="xs:NMTOKEN"/>
          <xs:attribute name="href" type="URI"/>
          <xs:attribute name="hreflang" type="LanguageCode"/>
          <xs:attribute name="rel" type="LinkTypes"/>
          <xs:attribute name="rev" type="LinkTypes"/>
          <xs:attribute name="shape" default="rect" type="Shape"/>
          <xs:attribute name="coords" type="Coords"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    ===================== Inline Elements ================================
    </xs:documentation>
  </xs:annotation>

  <xs:element name="span">
    <xs:annotation>
      <xs:documentation>
      generic language/style container
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="bdo">
    <xs:annotation>
      <xs:documentation>
      I18N BiDi over-ride
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="coreattrs"/>
          <xs:attributeGroup ref="events"/>
          <xs:attribute name="lang" type="LanguageCode"/>
          <xs:attribute ref="xml:lang"/>
          <xs:attribute name="dir" use="required">
            <xs:simpleType>
              <xs:restriction base="xs:token">
                <xs:enumeration value="ltr"/>
                <xs:enumeration value="rtl"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:attribute>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="br">
    <xs:annotation>
      <xs:documentation>
      forced line break
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attributeGroup ref="coreattrs"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="em">
    <xs:annotation>
      <xs:documentation>
      emphasis
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="strong">
    <xs:annotation>
      <xs:documentation>
      strong emphasis
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="dfn">
    <xs:annotation>
      <xs:documentation>
      definitional
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="code">
    <xs:annotation>
      <xs:documentation>
      program code
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="samp">
    <xs:annotation>
      <xs:documentation>
      sample
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="kbd">
    <xs:annotation>
      <xs:documentation>
      something user would type
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="var">
    <xs:annotation>
      <xs:documentation>
      variable
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="cite">
    <xs:annotation>
      <xs:documentation>
      citation
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="abbr">
    <xs:annotation>
      <xs:documentation>
      abbreviation
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="acronym">
    <xs:annotation>
      <xs:documentation>
      acronym
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="q">
    <xs:annotation>
      <xs:documentation>
      inlined quote
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
          <xs:attribute name="cite" type="URI"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="sub">
    <xs:annotation>
      <xs:documentation>
      subscript
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="sup">
    <xs:annotation>
      <xs:documentation>
      superscript
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="tt">
    <xs:annotation>
      <xs:documentation>
      fixed pitch font
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="i">
    <xs:annotation>
      <xs:documentation>
      italic font
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="b">
    <xs:annotation>
      <xs:documentation>
      bold font
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="big">
    <xs:annotation>
      <xs:documentation>
      bigger font
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="small">
    <xs:annotation>
      <xs:documentation>
      smaller font
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    ==================== Object ======================================

    object is used to embed objects as part of HTML pages.
    param elements should precede other content. Parameters
    can also be expressed as attribute/value pairs on the
    object element itself when brevity is desired.
    </xs:documentation>
  </xs:annotation>

  <xs:element name="object">
    <xs:complexType mixed="true">
      <xs:choice minOccurs="0" maxOccurs="unbounded">
        <xs:element ref="param"/>
        <xs:group ref="block"/>
        <xs:element ref="form"/>
        <xs:group ref="inline"/>
        <xs:group ref="misc"/>
      </xs:choice>
      <xs:attributeGroup ref="attrs"/>
      <xs:attribute name="declare">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="declare"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="classid" type="URI"/>
      <xs:attribute name="codebase" type="URI"/>
      <xs:attribute name="data" type="URI"/>
      <xs:attribute name="type" type="ContentType"/>
      <xs:attribute name="codetype" type="ContentType"/>
      <xs:attribute name="archive" type="UriList"/>
      <xs:attribute name="standby" type="Text"/>
      <xs:attribute name="height" type="Length"/>
      <xs:attribute name="width" type="Length"/>
      <xs:attribute name="usemap" type="URI"/>
      <xs:attribute name="name" type="xs:NMTOKEN"/>
      <xs:attribute name="tabindex" type="tabindexNumber"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="param">
    <xs:annotation>
      <xs:documentation>
      param is used to supply a named property value.
      In XML it would seem natural to follow RDF and support an
      abbreviated syntax where the param elements are replaced
      by attribute value pairs on the object start tag.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="id" type="xs:ID"/>
      <xs:attribute name="name"/>
      <xs:attribute name="value"/>
      <xs:attribute name="valuetype" default="data">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="data"/>
            <xs:enumeration value="ref"/>
            <xs:enumeration value="object"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="type" type="ContentType"/>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    =================== Images ===========================================

    To avoid accessibility problems for people who aren't
    able to see the image, you should provide a text
    description using the alt and longdesc attributes.
    In addition, avoid the use of server-side image maps.
    Note that in this DTD there is no name attribute. That
    is only available in the transitional and frameset DTD.
    </xs:documentation>
  </xs:annotation>

  <xs:element name="img">
    <xs:complexType>
      <xs:attributeGroup ref="attrs"/>
      <xs:attribute name="src" use="required" type="URI"/>
      <xs:attribute name="alt" use="required" type="Text"/>
      <xs:attribute name="longdesc" type="URI"/>
      <xs:attribute name="height" type="Length"/>
      <xs:attribute name="width" type="Length"/>
      <xs:attribute name="usemap" type="URI">
	<xs:annotation>
	  <xs:documentation>
          usemap points to a map element which may be in this document
          or an external document, although the latter is not widely supported
          </xs:documentation>
	</xs:annotation>
      </xs:attribute>
      <xs:attribute name="ismap">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="ismap"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    ================== Client-side image maps ============================

    These can be placed in the same document or grouped in a
    separate document although this isn't yet widely supported
    </xs:documentation>
  </xs:annotation>

  <xs:element name="map">
    <xs:complexType>
      <xs:choice>
        <xs:choice maxOccurs="unbounded">
          <xs:group ref="block"/>
          <xs:element ref="form"/>
          <xs:group ref="misc"/>
        </xs:choice>
        <xs:element maxOccurs="unbounded" ref="area"/>
      </xs:choice>
      <xs:attributeGroup ref="i18n"/>
      <xs:attributeGroup ref="events"/>
      <xs:attribute name="id" use="required" type="xs:ID"/>
      <xs:attribute name="class"/>
      <xs:attribute name="style" type="StyleSheet"/>
      <xs:attribute name="title" type="Text"/>
      <xs:attribute name="name" type="xs:NMTOKEN"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="area">
    <xs:complexType>
        <xs:attributeGroup ref="attrs"/>
      <xs:attributeGroup ref="focus"/>
      <xs:attribute name="shape" default="rect" type="Shape"/>
      <xs:attribute name="coords" type="Coords"/>
      <xs:attribute name="href" type="URI"/>
      <xs:attribute name="nohref">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="nohref"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="alt" use="required" type="Text"/>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    ================ Forms ===============================================
    </xs:documentation>
  </xs:annotation>

  <xs:element name="form">
    <xs:complexType>
      <xs:complexContent>
        <xs:extension base="form.content">
          <xs:attributeGroup ref="attrs"/>
          <xs:attribute name="action" use="required" type="URI"/>
          <xs:attribute name="method" default="get">
            <xs:simpleType>
              <xs:restriction base="xs:token">
                <xs:enumeration value="get"/>
                <xs:enumeration value="post"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:attribute>
          <xs:attribute name="enctype" type="ContentType"
              default="application/x-www-form-urlencoded"/>
          <xs:attribute name="onsubmit" type="Script"/>
          <xs:attribute name="onreset" type="Script"/>
          <xs:attribute name="accept" type="ContentTypes"/>
          <xs:attribute name="accept-charset" type="Charsets"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="label">
    <xs:annotation>
      <xs:documentation>
      Each label must not contain more than ONE field
      Label elements shouldn't be nested.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
          <xs:attribute name="for" type="xs:IDREF"/>
          <xs:attribute name="accesskey" type="Character"/>
          <xs:attribute name="onfocus" type="Script"/>
          <xs:attribute name="onblur" type="Script"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:simpleType name="InputType">
    <xs:restriction base="xs:token">
      <xs:enumeration value="text"/>
      <xs:enumeration value="password"/>
      <xs:enumeration value="checkbox"/>
      <xs:enumeration value="radio"/>
      <xs:enumeration value="submit"/>
      <xs:enumeration value="reset"/>
      <xs:enumeration value="file"/>
      <xs:enumeration value="hidden"/>
      <xs:enumeration value="image"/>
      <xs:enumeration value="button"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:element name="input">
    <xs:annotation>
      <xs:documentation>
      form control
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attributeGroup ref="attrs"/>
      <xs:attributeGroup ref="focus"/>
      <xs:attribute name="type" default="text" type="InputType"/>
      <xs:attribute name="name">
	<xs:annotation>
	  <xs:documentation>
          the name attribute is required for all but submit & reset
          </xs:documentation>
	</xs:annotation>
      </xs:attribute>
      <xs:attribute name="value"/>
      <xs:attribute name="checked">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="checked"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="disabled">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="disabled"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="readonly">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="readonly"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="size"/>
      <xs:attribute name="maxlength" type="Number"/>
      <xs:attribute name="src" type="URI"/>
      <xs:attribute name="alt"/>
      <xs:attribute name="usemap" type="URI"/>
      <xs:attribute name="onselect" type="Script"/>
      <xs:attribute name="onchange" type="Script"/>
      <xs:attribute name="accept" type="ContentTypes"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="select">
    <xs:annotation>
      <xs:documentation>
      option selector
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded">
        <xs:element ref="optgroup"/>
        <xs:element ref="option"/>
      </xs:choice>
      <xs:attributeGroup ref="attrs"/>
      <xs:attribute name="name"/>
      <xs:attribute name="size" type="Number"/>
      <xs:attribute name="multiple">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="multiple"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="disabled">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="disabled"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="tabindex" type="tabindexNumber"/>
      <xs:attribute name="onfocus" type="Script"/>
      <xs:attribute name="onblur" type="Script"/>
      <xs:attribute name="onchange" type="Script"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="optgroup">
    <xs:annotation>
      <xs:documentation>
      option group
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" ref="option"/>
      </xs:sequence>
      <xs:attributeGroup ref="attrs"/>
      <xs:attribute name="disabled">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="disabled"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="label" use="required" type="Text"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="option">
    <xs:annotation>
      <xs:documentation>
      selectable choice
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:attributeGroup ref="attrs"/>
      <xs:attribute name="selected">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="selected"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="disabled">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="disabled"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="label" type="Text"/>
      <xs:attribute name="value"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="textarea">
    <xs:annotation>
      <xs:documentation>
      multi-line text field
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:attributeGroup ref="attrs"/>
      <xs:attributeGroup ref="focus"/>
      <xs:attribute name="name"/>
      <xs:attribute name="rows" use="required" type="Number"/>
      <xs:attribute name="cols" use="required" type="Number"/>
      <xs:attribute name="disabled">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="disabled"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="readonly">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="readonly"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="onselect" type="Script"/>
      <xs:attribute name="onchange" type="Script"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="fieldset">
    <xs:annotation>
      <xs:documentation>
      The fieldset element is used to group form fields.
      Only one legend element should occur in the content
      and if present should only be preceded by whitespace.

      NOTE: this content model is different from the XHTML 1.0 DTD,
      closer to the intended content model in HTML4 DTD
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:sequence>
        <xs:element ref="legend"/>
        <xs:choice minOccurs="0" maxOccurs="unbounded">
          <xs:group ref="block"/>
          <xs:element ref="form"/>
          <xs:group ref="inline"/>
          <xs:group ref="misc"/>
        </xs:choice>
      </xs:sequence>
      <xs:attributeGroup ref="attrs"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="legend">
    <xs:annotation>
      <xs:documentation>
      fieldset label
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
          <xs:attribute name="accesskey" type="Character"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="button">
    <xs:annotation>
      <xs:documentation>
      Content is "Flow" excluding a, form and form controls
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="button.content">
          <xs:attributeGroup ref="attrs"/>
          <xs:attributeGroup ref="focus"/>
          <xs:attribute name="name"/>
          <xs:attribute name="value"/>
          <xs:attribute name="type" default="submit">
            <xs:simpleType>
              <xs:restriction base="xs:token">
                <xs:enumeration value="button"/>
                <xs:enumeration value="submit"/>
                <xs:enumeration value="reset"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:attribute>
          <xs:attribute name="disabled">
            <xs:simpleType>
              <xs:restriction base="xs:token">
                <xs:enumeration value="disabled"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:attribute>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    ======================= Tables =======================================

    Derived from IETF HTML table standard, see [RFC1942]
    </xs:documentation>
  </xs:annotation>

  <xs:simpleType name="TFrame">
    <xs:annotation>
      <xs:documentation>
      The border attribute sets the thickness of the frame around the
      table. The default units are screen pixels.

      The frame attribute specifies which parts of the frame around
      the table should be rendered. The values are not the same as
      CALS to avoid a name clash with the valign attribute.
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:token">
      <xs:enumeration value="void"/>
      <xs:enumeration value="above"/>
      <xs:enumeration value="below"/>
      <xs:enumeration value="hsides"/>
      <xs:enumeration value="lhs"/>
      <xs:enumeration value="rhs"/>
      <xs:enumeration value="vsides"/>
      <xs:enumeration value="box"/>
      <xs:enumeration value="border"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="TRules">
    <xs:annotation>
      <xs:documentation>
      The rules attribute defines which rules to draw between cells:

      If rules is absent then assume:
          "none" if border is absent or border="0" otherwise "all"
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:token">
      <xs:enumeration value="none"/>
      <xs:enumeration value="groups"/>
      <xs:enumeration value="rows"/>
      <xs:enumeration value="cols"/>
      <xs:enumeration value="all"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:attributeGroup name="cellhalign">
    <xs:annotation>
      <xs:documentation>
      horizontal alignment attributes for cell contents

      char        alignment char, e.g. char=':'
      charoff     offset for alignment char
      </xs:documentation>
    </xs:annotation>
    <xs:attribute name="align">
      <xs:simpleType>
        <xs:restriction base="xs:token">
          <xs:enumeration value="left"/>
          <xs:enumeration value="center"/>
          <xs:enumeration value="right"/>
          <xs:enumeration value="justify"/>
          <xs:enumeration value="char"/>
        </xs:restriction>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute name="char" type="Character"/>
    <xs:attribute name="charoff" type="Length"/>
  </xs:attributeGroup>

  <xs:attributeGroup name="cellvalign">
    <xs:annotation>
      <xs:documentation>
      vertical alignment attributes for cell contents
      </xs:documentation>
    </xs:annotation>
    <xs:attribute name="valign">
      <xs:simpleType>
        <xs:restriction base="xs:token">
          <xs:enumeration value="top"/>
          <xs:enumeration value="middle"/>
          <xs:enumeration value="bottom"/>
          <xs:enumeration value="baseline"/>
        </xs:restriction>
      </xs:simpleType>
    </xs:attribute>
  </xs:attributeGroup>

  <xs:element name="table">
    <xs:complexType>
      <xs:sequence>
        <xs:element minOccurs="0" ref="caption"/>
        <xs:choice>
          <xs:element minOccurs="0" maxOccurs="unbounded" ref="col"/>
          <xs:element minOccurs="0" maxOccurs="unbounded" ref="colgroup"/>
        </xs:choice>
        <xs:element minOccurs="0" ref="thead"/>
        <xs:element minOccurs="0" ref="tfoot"/>
        <xs:choice>
          <xs:element maxOccurs="unbounded" ref="tbody"/>
          <xs:element maxOccurs="unbounded" ref="tr"/>
        </xs:choice>
      </xs:sequence>
      <xs:attributeGroup ref="attrs"/>
      <xs:attribute name="summary" type="Text"/>
      <xs:attribute name="width" type="Length"/>
      <xs:attribute name="border" type="Pixels"/>
      <xs:attribute name="frame" type="TFrame"/>
      <xs:attribute name="rules" type="TRules"/>
      <xs:attribute name="cellspacing" type="Length"/>
      <xs:attribute name="cellpadding" type="Length"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="caption">
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    Use thead to duplicate headers when breaking table
    across page boundaries, or for static headers when
    tbody sections are rendered in scrolling panel.

    Use tfoot to duplicate footers when breaking table
    across page boundaries, or for static footers when
    tbody sections are rendered in scrolling panel.

    Use multiple tbody sections when rules are needed
    between groups of table rows.
    </xs:documentation>
  </xs:annotation>

  <xs:element name="thead">
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" ref="tr"/>
      </xs:sequence>
      <xs:attributeGroup ref="attrs"/>
      <xs:attributeGroup ref="cellhalign"/>
      <xs:attributeGroup ref="cellvalign"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="tfoot">
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" ref="tr"/>
      </xs:sequence>
      <xs:attributeGroup ref="attrs"/>
      <xs:attributeGroup ref="cellhalign"/>
      <xs:attributeGroup ref="cellvalign"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="tbody">
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" ref="tr"/>
      </xs:sequence>
      <xs:attributeGroup ref="attrs"/>
      <xs:attributeGroup ref="cellhalign"/>
      <xs:attributeGroup ref="cellvalign"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="colgroup">
    <xs:annotation>
      <xs:documentation>
      colgroup groups a set of col elements. It allows you to group
      several semantically related columns together.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:sequence>
        <xs:element minOccurs="0" maxOccurs="unbounded" ref="col"/>
      </xs:sequence>
      <xs:attributeGroup ref="attrs"/>
      <xs:attribute name="span" default="1" type="Number"/>
      <xs:attribute name="width" type="MultiLength"/>
      <xs:attributeGroup ref="cellhalign"/>
      <xs:attributeGroup ref="cellvalign"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="col">
    <xs:annotation>
      <xs:documentation>
      col elements define the alignment properties for cells in
      one or more columns.

      The width attribute specifies the width of the columns, e.g.

          width=64        width in screen pixels
          width=0.5*      relative width of 0.5

      The span attribute causes the attributes of one
      col element to apply to more than one column.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attributeGroup ref="attrs"/>
      <xs:attribute name="span" default="1" type="Number"/>
      <xs:attribute name="width" type="MultiLength"/>
      <xs:attributeGroup ref="cellhalign"/>
      <xs:attributeGroup ref="cellvalign"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="tr">
    <xs:complexType>
      <xs:choice maxOccurs="unbounded">
        <xs:element ref="th"/>
        <xs:element ref="td"/>
      </xs:choice>
      <xs:attributeGroup ref="attrs"/>
      <xs:attributeGroup ref="cellhalign"/>
      <xs:attributeGroup ref="cellvalign"/>
    </xs:complexType>
  </xs:element>

  <xs:simpleType name="Scope">
    <xs:annotation>
      <xs:documentation>
      Scope is simpler than headers attribute for common tables
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:token">
      <xs:enumeration value="row"/>
      <xs:enumeration value="col"/>
      <xs:enumeration value="rowgroup"/>
      <xs:enumeration value="colgroup"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:annotation>
    <xs:documentation>
    th is for headers, td for data and for cells acting as both
    </xs:documentation>
  </xs:annotation>

  <xs:element name="th">
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Flow">
          <xs:attributeGroup ref="attrs"/>
          <xs:attribute name="abbr" type="Text"/>
          <xs:attribute name="axis"/>
          <xs:attribute name="headers" type="xs:IDREFS"/>
          <xs:attribute name="scope" type="Scope"/>
          <xs:attribute name="rowspan" default="1" type="Number"/>
          <xs:attribute name="colspan" default="1" type="Number"/>
          <xs:attributeGroup ref="cellhalign"/>
          <xs:attributeGroup ref="cellvalign"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="td">
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Flow">
          <xs:attributeGroup ref="attrs"/>
          <xs:attribute name="abbr" type="Text"/>
          <xs:attribute name="axis"/>
          <xs:attribute name="headers" type="xs:IDREFS"/>
          <xs:attribute name="scope" type="Scope"/>
          <xs:attribute name="rowspan" default="1" type="Number"/>
          <xs:attribute name="colspan" default="1" type="Number"/>
          <xs:attributeGroup ref="cellhalign"/>
          <xs:attributeGroup ref="cellvalign"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

</xs:schema>

Index: modules/damieng/graphical_editor/daxe/web/config/xml.xsd
+++ modules/damieng/graphical_editor/daxe/web/config/xml.xsd
<?xml version='1.0'?>
<!-- <!DOCTYPE xs:schema PUBLIC "-//W3C//DTD XMLSCHEMA 200102//EN" "XMLSchema.dtd" > -->
<xs:schema targetNamespace="http://www.w3.org/XML/1998/namespace" xmlns:xs="http://www.w3.org/2001/XMLSchema" xml:lang="en">

 <xs:annotation>
  <xs:documentation>
   See http://www.w3.org/XML/1998/namespace.html and
   http://www.w3.org/TR/REC-xml for information about this namespace.

    This schema document describes the XML namespace, in a form
    suitable for import by other schema documents.  

    Note that local names in this namespace are intended to be defined
    only by the World Wide Web Consortium or its subgroups.  The
    following names are currently defined in this namespace and should
    not be used with conflicting semantics by any Working Group,
    specification, or document instance:

    base (as an attribute name): denotes an attribute whose value
         provides a URI to be used as the base for interpreting any
         relative URIs in the scope of the element on which it
         appears; its value is inherited.  This name is reserved
         by virtue of its definition in the XML Base specification.

    id   (as an attribute name): denotes an attribute whose value
         should be interpreted as if declared to be of type ID.
         The xml:id specification is not yet a W3C Recommendation,
         but this attribute is included here to facilitate experimentation
         with the mechanisms it proposes.  Note that it is _not_ included
         in the specialAttrs attribute group.

    lang (as an attribute name): denotes an attribute whose value
         is a language code for the natural language of the content of
         any element; its value is inherited.  This name is reserved
         by virtue of its definition in the XML specification.
  
    space (as an attribute name): denotes an attribute whose
         value is a keyword indicating what whitespace processing
         discipline is intended for the content of the element; its
         value is inherited.  This name is reserved by virtue of its
         definition in the XML specification.

    Father (in any context at all): denotes Jon Bosak, the chair of 
         the original XML Working Group.  This name is reserved by 
         the following decision of the W3C XML Plenary and 
         XML Coordination groups:

             In appreciation for his vision, leadership and dedication
             the W3C XML Plenary on this 10th day of February, 2000
             reserves for Jon Bosak in perpetuity the XML name
             xml:Father
  </xs:documentation>
 </xs:annotation>

 <xs:annotation>
  <xs:documentation>This schema defines attributes and an attribute group
        suitable for use by
        schemas wishing to allow xml:base, xml:lang or xml:space attributes
        on elements they define.

        To enable this, such a schema must import this schema
        for the XML namespace, e.g. as follows:
        <schema . . .>
         . . .
         <import namespace="http://www.w3.org/XML/1998/namespace"
                    schemaLocation="http://www.w3.org/2001/03/xml.xsd"/>

        Subsequently, qualified reference to any of the attributes
        or the group defined below will have the desired effect, e.g.

        <type . . .>
         . . .
         <attributeGroup ref="xml:specialAttrs"/>
 
         will define a type which will schema-validate an instance
         element with any of those attributes</xs:documentation>
 </xs:annotation>

 <xs:annotation>
  <xs:documentation>In keeping with the XML Schema WG's standard versioning
   policy, this schema document will persist at
   http://www.w3.org/2004/10/xml.xsd.
   At the date of issue it can also be found at
   http://www.w3.org/2001/xml.xsd.
   The schema document at that URI may however change in the future,
   in order to remain compatible with the latest version of XML Schema
   itself, or with the XML namespace itself.  In other words, if the XML
   Schema or XML namespaces change, the version of this document at
   http://www.w3.org/2001/xml.xsd will change
   accordingly; the version at
   http://www.w3.org/2004/10/xml.xsd will not change.
  </xs:documentation>
 </xs:annotation>

 <xs:attribute name="lang" type="xs:language">
  <xs:annotation>
   <xs:documentation>Attempting to install the relevant ISO 2- and 3-letter
         codes as the enumerated possible values is probably never
         going to be a realistic possibility.  See
         RFC 3066 at http://www.ietf.org/rfc/rfc3066.txt and the IANA registry
         at http://www.iana.org/assignments/lang-tag-apps.htm for
         further information.</xs:documentation>
  </xs:annotation>
 </xs:attribute>

 <xs:attribute name="space">
  <xs:simpleType>
   <xs:restriction base="xs:NCName">
    <xs:enumeration value="default"/>
    <xs:enumeration value="preserve"/>
   </xs:restriction>
  </xs:simpleType>
 </xs:attribute>

 <xs:attribute name="base" type="xs:anyURI">
  <xs:annotation>
   <xs:documentation>See http://www.w3.org/TR/xmlbase/ for
                     information about this attribute.</xs:documentation>
  </xs:annotation>
 </xs:attribute>
 
 <xs:attribute name="id" type="xs:ID">
  <xs:annotation>
   <xs:documentation>See http://www.w3.org/TR/xml-id/ for
                     information about this attribute.</xs:documentation>
  </xs:annotation>
 </xs:attribute>

 <xs:attributeGroup name="specialAttrs">
  <xs:attribute ref="xml:base"/>
  <xs:attribute ref="xml:lang"/>
  <xs:attribute ref="xml:space"/>
 </xs:attributeGroup>

</xs:schema>

Index: modules/damieng/graphical_editor/loncapa_daxe/build.sh
+++ modules/damieng/graphical_editor/loncapa_daxe/build.sh
#!/bin/sh
# NOTE: dart2js is in dart-sdk/bin, which should be on the PATH
dart2js --minify --out=loncapa_daxe.min.dart.js web/loncapa_daxe.dart
dart2js --output-type=dart --minify --out=loncapa_daxe.min.dart web/loncapa_daxe.dart
rm *.js.map *.deps

Index: modules/damieng/graphical_editor/loncapa_daxe/daxe.html
+++ modules/damieng/graphical_editor/loncapa_daxe/daxe.html
<!DOCTYPE html>

<html>
  <head>
    <meta charset="utf-8">
    <title>LON-CAPA Daxe</title>
    <link rel="stylesheet" href="packages/daxe/daxe.css">
    <link rel="stylesheet" href="loncapa_daxe.css">
    <script type="text/javascript"
      src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
    </script>
    <script src="LC_math_editor.min.js"></script>
    <script src="loncapa_daxe.min.dart.js"></script>
</head>
  <body>
    <textarea id="tacursor"></textarea><span id="caret"></span>
    <div id="headers"></div>
    <div id="doc1"><div id="doc2"></div></div>
    <div id="path"></div>
  </body>
</html>

Index: modules/damieng/graphical_editor/loncapa_daxe/pubspec.lock
+++ modules/damieng/graphical_editor/loncapa_daxe/pubspec.lock
# Generated by pub
# See http://pub.dartlang.org/doc/glossary.html#lockfile
packages:
  analyzer:
    description: analyzer
    source: hosted
    version: "0.22.2"
  args:
    description: args
    source: hosted
    version: "0.12.0+1"
  browser:
    description: browser
    source: hosted
    version: "0.10.0+2"
  collection:
    description: collection
    source: hosted
    version: "0.9.4"
  crypto:
    description: crypto
    source: hosted
    version: "0.9.0"
  daxe:
    description:
      path: "../daxe"
      relative: true
    source: path
    version: "0.0.0"
  intl:
    description: intl
    source: hosted
    version: "0.11.5"
  js:
    description: js
    source: hosted
    version: "0.2.2"
  logging:
    description: logging
    source: hosted
    version: "0.9.2"
  meta:
    description: meta
    source: hosted
    version: "0.8.8"
  path:
    description: path
    source: hosted
    version: "1.3.0"
  stack_trace:
    description: stack_trace
    source: hosted
    version: "1.0.2"
  watcher:
    description: watcher
    source: hosted
    version: "0.9.3"

Index: modules/damieng/graphical_editor/loncapa_daxe/pubspec.yaml
+++ modules/damieng/graphical_editor/loncapa_daxe/pubspec.yaml
name: loncapa_daxe
author: Damien Guillaume
description: LON-CAPA graphical editor
dependencies:
  browser: any
  daxe:
    path: ../daxe
  js: any

Index: modules/damieng/graphical_editor/loncapa_daxe/.settings/org.eclipse.core.resources.prefs
+++ modules/damieng/graphical_editor/loncapa_daxe/.settings/org.eclipse.core.resources.prefs
eclipse.preferences.version=1
encoding//web/LocalStrings_en.properties=UTF-8
encoding//web/LocalStrings_fr.properties=UTF-8

Index: modules/damieng/graphical_editor/loncapa_daxe/web/LC_math_editor.min.js
+++ modules/damieng/graphical_editor/loncapa_daxe/web/LC_math_editor.min.js
'use strict';var LCMATH=function(){function l(){this.operators=[]}function e(b,a,c,g,f){this.type=b;this.op=a;this.value=c;this.children=g;this.interval_type="undefined"==typeof f?e.NOT_AN_INTERVAL:f}function p(b,a,c,g,f,e){this.id=b;this.arity=a;this.lbp=c;this.rbp=g;this.nud=f;this.led=e}function n(b,a,c){this.msg=b;this.from=a;this.to=c?c:this.from}function r(b,a,c){this.implicit_operators="undefined"==typeof b?!1:b;this.unit_mode="undefined"==typeof a?!1:a;this.constants="undefined"==typeof c||
null==c?[]:c;this.defs=new l;this.defs.define();this.operators=this.defs.operators;this.oph={};for(b=0;b<this.operators.length;b++)this.oph[this.operators[b].id]=this.operators[b]}function t(b,a){this.defs=b;this.text=a}function k(b,a,c,g,f){this.type=b;this.from=a;this.to=c;this.value=g;this.op=f}l.ARG_SEPARATOR=";";l.DECIMAL_SIGN_1=".";l.DECIMAL_SIGN_2=",";l.INTERVAL_SEPARATOR=":";l.prototype.operator=function(b,a,c,g,f,e){this.operators.push(new p(b,a,c,g,f,e))};l.prototype.separator=function(b){this.operator(b,
p.BINARY,0,0,null,null)};l.prototype.infix=function(b,a,c,g){var f;f=p.BINARY;g=g||function(a,d){var f=[d,a.expression(c)];return new e(e.OPERATOR,this,b,f)};this.operator(b,f,a,c,null,g)};l.prototype.prefix=function(b,a,c){var g;g=p.UNARY;c=c||function(c){c=[c.expression(a)];return new e(e.OPERATOR,this,b,c)};this.operator(b,g,0,a,c,null)};l.prototype.suffix=function(b,a,c){var g;g=p.UNARY;c=c||function(a,c){return new e(e.OPERATOR,this,b,[c])};this.operator(b,g,a,0,null,c)};l.prototype.findOperator=
function(b){for(var a=0;a<this.operators.length;a++)if(this.operators[a].id==b)return this.operators[a];return null};l.prototype.buildInterval=function(b,a,c,g){g.advance(l.INTERVAL_SEPARATOR);var f=g.expression(0);if(null==g.current_token||null==g.current_token.op||")"!==g.current_token.op.id&&"]"!==g.current_token.op.id)throw new n("Wrong interval syntax.",g.tokens[g.token_nr-1].from);")"==g.current_token.op.id?(g.advance(")"),b=b?e.CLOSED_OPEN:e.OPEN_OPEN):(g.advance("]"),b=b?e.CLOSED_CLOSED:e.OPEN_CLOSED);
return new e(e.INTERVAL,c,null,[a,f],b)};l.prototype.define=function(){this.suffix("!",160);this.infix("^",140,139);this.infix(".",130,129);this.infix("`",125,125,function(a,b){for(var g=a.expression(125);null!=a.current_token&&-1!="*/".indexOf(a.current_token.value);){var f=a.tokens[a.token_nr];if(null==f)break;if(f.type!=k.NAME&&"("!=f.value)break;f=a.tokens[a.token_nr+1];if(null!=f&&("("==f.value||f.type==k.NUMBER))break;if(a.unit_mode&&a.tokens[a.token_nr].type==k.NAME){for(var f=a.tokens[a.token_nr].value,
m=!1,d=0;d<a.constants.length;d++)if(f==a.constants[d]){m=!0;break}if(m)break}f=a.current_token;a.advance();g=f.op.led(a,g)}return new e(e.OPERATOR,this,"`",[b,g])});this.infix("*",120,120);this.infix("/",120,120);this.infix("%",120,120);this.infix("+",100,100);this.operator("-",p.BINARY,100,134,function(a){a=[a.expression(134)];return new e(e.OPERATOR,this,"-",a)},function(a,b){var g=[b,a.expression(100)];return new e(e.OPERATOR,this,"-",g)});this.infix("=",80,80);this.infix("#",80,80);this.infix("<=",
80,80);this.infix(">=",80,80);this.infix("<",80,80);this.infix(">",80,80);this.separator(")");this.separator(l.ARG_SEPARATOR);this.separator(l.INTERVAL_SEPARATOR);var b=this;this.operator("(",p.BINARY,200,200,function(a){var c=a.expression(0);if(null!=a.current_token&&null!=a.current_token.op&&a.current_token.op.id==l.INTERVAL_SEPARATOR)return b.buildInterval(!1,c,this,a);a.advance(")");return c},function(a,b){if(b.type!=e.NAME&&b.type!=e.SUBSCRIPT)throw new n("Function name expected before a parenthesis.",
a.tokens[a.token_nr-1].from);var g=[b];if(null==a.current_token||null==a.current_token.op||")"!==a.current_token.op.id)for(;;){g.push(a.expression(0));if(null==a.current_token||null==a.current_token.op||a.current_token.op.id!==l.ARG_SEPARATOR)break;a.advance(l.ARG_SEPARATOR)}a.advance(")");return new e(e.FUNCTION,this,"(",g)});this.separator("]");this.operator("[",p.BINARY,200,70,function(a){var c=[];if(null==a.current_token||null==a.current_token.op||"]"!==a.current_token.op.id){var g=a.expression(0);
if(null!=a.current_token&&null!=a.current_token.op&&a.current_token.op.id==l.INTERVAL_SEPARATOR)return b.buildInterval(!0,g,this,a);for(;;){c.push(g);if(null==a.current_token||null==a.current_token.op||a.current_token.op.id!==l.ARG_SEPARATOR)break;a.advance(l.ARG_SEPARATOR);g=a.expression(0)}}a.advance("]");return new e(e.VECTOR,this,null,c)},function(a,b){if(b.type!=e.NAME&&b.type!=e.SUBSCRIPT)throw new n("Name expected before a square bracket.",a.tokens[a.token_nr-1].from);var g=[b];if(null==a.current_token||
null==a.current_token.op||"]"!==a.current_token.op.id)for(;;){g.push(a.expression(0));if(null==a.current_token||null==a.current_token.op||a.current_token.op.id!==l.ARG_SEPARATOR)break;a.advance(l.ARG_SEPARATOR)}a.advance("]");return new e(e.SUBSCRIPT,this,"[",g)});this.separator("}");this.prefix("{",200,function(a){var b=[];if(null==a.current_token||null==a.current_token.op||"}"!==a.current_token.op.id)for(;;){b.push(a.expression(0));if(null==a.current_token||null==a.current_token.op||a.current_token.op.id!==
l.ARG_SEPARATOR)break;a.advance(l.ARG_SEPARATOR)}a.advance("}");return new e(e.SET,this,null,b)});this.prefix("$",300,function(a){var b=a.expression(300);if(b.type!=e.NAME)throw new n("Variable name expected after a $.",a.tokens[a.token_nr-1].from);b.value="$"+b.value;return b})};"use strict";e.UNKNOWN=0;e.NAME=1;e.NUMBER=2;e.OPERATOR=3;e.FUNCTION=4;e.VECTOR=5;e.INTERVAL=6;e.SET=7;e.SUBSCRIPT=8;e.COLORS="#E01010 #0010FF #009000 #FF00FF #00B0B0 #F09000 #800080 #F080A0 #6090F0 #902000 #70A050 #A07060 #5000FF #E06050 #008080 #808000".split(" ");
e.NOT_AN_INTERVAL=0;e.OPEN_OPEN=1;e.OPEN_CLOSED=2;e.CLOSED_OPEN=3;e.CLOSED_CLOSED=4;e.prototype.toString=function(){var b="(";switch(this.type){case e.UNKNOWN:b+="UNKNOWN";break;case e.NAME:b+="NAME";break;case e.NUMBER:b+="NUMBER";break;case e.OPERATOR:b+="OPERATOR";break;case e.FUNCTION:b+="FUNCTION";break;case e.VECTOR:b+="VECTOR";break;case e.INTERVAL:b+="INTERVAL";break;case e.SET:b+="SET";break;case e.SUBSCRIPT:b+="SUBSCRIPT"}this.op&&(b+=" '"+this.op.id+"'");this.value&&(b+=" '"+this.value+
"'");if(this.children){for(var b=b+" [",a=0;a<this.children.length;a++)b+=this.children[a].toString(),a!=this.children.length-1&&(b+=",");b+="]"}this.interval_type&&(b+=" "+this.interval_type);return b+")"};e.prototype.getColorForIdentifier=function(b,a){var c=a.hcolors[b];c||(c=a.colors?a.colors:e.COLORS,c=c[Object.keys(a.hcolors).length%c.length],a.hcolors[b]=c);return c};e.prototype.toMathML=function(b){return this._toMathML({hcolors:{},depth:0,colors:b})};e.prototype._toMathML=function(b){var a,
c,g,f,m,d,h;a=null!=this.children&&0<this.children.length?this.children[0]:null;c=null!=this.children&&1<this.children.length?this.children[1]:null;g=null!=this.children&&2<this.children.length?this.children[2]:null;f=null!=this.children&&3<this.children.length?this.children[3]:null;m=null!=this.children&&4<this.children.length?this.children[4]:null;switch(this.type){case e.UNKNOWN:return d=document.createElement("mtext"),d.appendChild(document.createTextNode("???")),d;case e.NAME:return 0<=this.value.search(/^[a-zA-Z]+[0-9]+$/)?
(d=this.value.search(/[0-9]/),g=document.createElement("msub"),g.appendChild(this.mi(this.value.substring(0,d))),g.appendChild(this.mn(this.value.substring(d))),d=g):d=this.mi(this.value),d.setAttribute("mathcolor",this.getColorForIdentifier(this.value,b)),0===this.value.indexOf("$")&&d.setAttribute("fontfamily","monospace"),d;case e.NUMBER:return-1!=this.value.indexOf("e")||-1!=this.value.indexOf("E")?(b=this.value.indexOf("e"),-1==b&&(b=this.value.indexOf("E")),d=document.createElement("mrow"),
d.appendChild(this.mn(this.value.substring(0,b))),d.appendChild(this.mo("\u22c5")),a=document.createElement("msup"),a.appendChild(this.mn(10)),a.appendChild(this.mn(this.value.substring(b+1))),d.appendChild(a),d):this.mn(this.value);case e.OPERATOR:if("/"==this.value)m=document.createElement("mfrac"),m.appendChild(a._toMathML(b)),m.appendChild(c._toMathML(b)),d=m;else if("^"==this.value)g=a.type==e.FUNCTION?"sqrt"==a.value||"abs"==a.value||"matrix"==a.value||"diff"==a.value?!1:!0:a.type==e.OPERATOR?
!0:!1,d=document.createElement("msup"),g?d.appendChild(this.addP(a,b)):d.appendChild(a._toMathML(b)),d.appendChild(c._toMathML(b));else if("*"==this.value){d=document.createElement("mrow");a.type!=e.OPERATOR||"+"!=a.value&&"-"!=a.value?d.appendChild(a._toMathML(b)):d.appendChild(this.addP(a,b));for(g=c;g.type==e.OPERATOR;)g=g.children[0];a.type==e.VECTOR&&c.type==e.VECTOR?d.appendChild(this.mo("*")):g.type==e.NUMBER?d.appendChild(this.mo("\u22c5")):b.units?d.appendChild(this.mo("\u22c5")):d.appendChild(this.mo("\u2062"));
c.type!=e.OPERATOR||"+"!=c.value&&"-"!=c.value?d.appendChild(c._toMathML(b)):d.appendChild(this.addP(c,b))}else if("-"==this.value)d=document.createElement("mrow"),1==this.children.length?(d.appendChild(this.mo("-")),d.appendChild(a._toMathML(b))):(d.appendChild(a._toMathML(b)),d.appendChild(this.mo("-")),c.type!=e.OPERATOR||"+"!=c.value&&"-"!=c.value?d.appendChild(c._toMathML(b)):d.appendChild(this.addP(c,b)));else if("!"==this.value)d=document.createElement("mrow"),h=this.mo(this.value),a.type!=
e.OPERATOR||"+"!=a.value&&"-"!=a.value?d.appendChild(a._toMathML(b)):d.appendChild(this.addP(a,b)),d.appendChild(h);else if("+"==this.value){d=document.createElement("mrow");h=this.mo(this.value);d.appendChild(a._toMathML(b));d.appendChild(h);g=!1;for(f=c;f.type==e.OPERATOR;)if("-"==f.value&&1==f.children.length){g=!0;break}else if("+"==f.value||"-"==f.value||"*"==f.value)f=f.children[0];else break;g?d.appendChild(this.addP(c,b)):d.appendChild(c._toMathML(b))}else if("."==this.value)d=document.createElement("mrow"),
a.type!=e.OPERATOR||"+"!=a.value&&"-"!=a.value?d.appendChild(a._toMathML(b)):d.appendChild(this.addP(a,b)),d.appendChild(this.mo("\u22c5")),c.type!=e.OPERATOR||"+"!=c.value&&"-"!=c.value?d.appendChild(c._toMathML(b)):d.appendChild(this.addP(c,b));else if("`"==this.value){d=document.createElement("mrow");a.type!=e.OPERATOR||"+"!=a.value&&"-"!=a.value?d.appendChild(a._toMathML(b)):d.appendChild(this.addP(a,b));g=document.createElement("mstyle");g.setAttribute("fontstyle","normal");f={};for(h in b)b.hasOwnProperty(h)&&
(f[h]=b[h]);f.units=!0;c.type!=e.OPERATOR||"+"!=c.value&&"-"!=c.value?g.appendChild(c._toMathML(f)):g.appendChild(this.addP(c,f));d.appendChild(g)}else d=document.createElement("mrow"),h=this.mo(this.value),d.appendChild(a._toMathML(b)),d.appendChild(h),d.appendChild(c._toMathML(b));return d;case e.FUNCTION:if("sqrt"==a.value&&null!=c)d=document.createElement("msqrt"),d.appendChild(c._toMathML(b));else if("abs"==a.value&&null!=c)d=document.createElement("mrow"),d.appendChild(this.mo("|")),d.appendChild(c._toMathML(b)),
d.appendChild(this.mo("|"));else if("exp"==a.value&&null!=c)d=document.createElement("msup"),d.appendChild(this.mi("e")),d.appendChild(c._toMathML(b));else if("factorial"==a.value)d=document.createElement("mrow"),h=this.mo("!"),c.type!=e.OPERATOR||"+"!=c.value&&"-"!=c.value?d.appendChild(c._toMathML(b)):d.appendChild(this.addP(c,b)),d.appendChild(h);else if("diff"==a.value&&null!=this.children&&3==this.children.length)d=document.createElement("mrow"),m=document.createElement("mfrac"),m.appendChild(this.mi("d")),
h=document.createElement("mrow"),h.appendChild(this.mi("d")),h.appendChild(this.mi(g.value)),m.appendChild(h),d.appendChild(m),c.type!=e.OPERATOR||"+"!=c.value&&"-"!=c.value?d.appendChild(c._toMathML(b)):d.appendChild(this.addP(c,b));else if("diff"==a.value&&null!=this.children&&4==this.children.length)d=document.createElement("mrow"),m=document.createElement("mfrac"),a=document.createElement("msup"),a.appendChild(this.mi("d")),a.appendChild(f._toMathML(b)),m.appendChild(a),h=document.createElement("mrow"),
h.appendChild(this.mi("d")),a=document.createElement("msup"),a.appendChild(g._toMathML(b)),a.appendChild(f._toMathML(b)),h.appendChild(a),m.appendChild(h),d.appendChild(m),c.type!=e.OPERATOR||"+"!=c.value&&"-"!=c.value?d.appendChild(c._toMathML(b)):d.appendChild(this.addP(c,b));else if("integrate"==a.value&&null!=this.children&&3==this.children.length)d=document.createElement("mrow"),h=this.mo("\u222b"),h.setAttribute("stretchy","true"),d.appendChild(h),g.type!=e.OPERATOR||"+"!=g.value&&"-"!=g.value?
d.appendChild(c._toMathML(b)):d.appendChild(this.addP(c,b)),d.appendChild(this.mi("d")),d.appendChild(g._toMathML(b));else if("integrate"==a.value&&null!=this.children&&5==this.children.length)d=document.createElement("mrow"),a=document.createElement("msubsup"),h=this.mo("\u222b"),h.setAttribute("stretchy","true"),a.appendChild(h),a.appendChild(f._toMathML(b)),a.appendChild(m._toMathML(b)),d.appendChild(a),g.type!=e.OPERATOR||"+"!=g.value&&"-"!=g.value?d.appendChild(c._toMathML(b)):d.appendChild(this.addP(c,
b)),d.appendChild(this.mi("d")),d.appendChild(g._toMathML(b));else if("sum"==a.value&&null!=this.children&&5==this.children.length){d=document.createElement("mrow");var k=document.createElement("munderover");h=this.mo("\u2211");h.setAttribute("stretchy","true");k.appendChild(h);a=document.createElement("mrow");a.appendChild(g._toMathML(b));a.appendChild(this.mo("="));a.appendChild(f._toMathML(b));k.appendChild(a);k.appendChild(m._toMathML(b));d.appendChild(k);g.type!=e.OPERATOR||"+"!=g.value&&"-"!=
g.value?d.appendChild(c._toMathML(b)):d.appendChild(this.addP(c,b))}else if("product"==a.value&&null!=this.children&&5==this.children.length)d=document.createElement("mrow"),k=document.createElement("munderover"),h=this.mo("\u220f"),h.setAttribute("stretchy","true"),k.appendChild(h),a=document.createElement("mrow"),a.appendChild(g._toMathML(b)),a.appendChild(this.mo("=")),a.appendChild(f._toMathML(b)),k.appendChild(a),k.appendChild(m._toMathML(b)),d.appendChild(k),g.type!=e.OPERATOR||"+"!=g.value&&
"-"!=g.value?d.appendChild(c._toMathML(b)):d.appendChild(this.addP(c,b));else if("limit"==a.value)d=document.createElement("mrow"),4>this.children.length?d.appendChild(this.mo("lim")):(a=document.createElement("munder"),a.appendChild(this.mo("lim")),h=document.createElement("mrow"),h.appendChild(g._toMathML(b)),h.appendChild(this.mo("\u2192")),h.appendChild(f._toMathML(b)),null!=m&&("plus"==m.value?h.appendChild(this.mo("+")):"minus"==m.value&&h.appendChild(this.mo("-"))),a.appendChild(h),d.appendChild(a)),
d.appendChild(c._toMathML(b));else if("binomial"==a.value){d=document.createElement("mrow");d.appendChild(this.mo("("));g=document.createElement("mtable");for(f=1;f<this.children.length;f++)a=document.createElement("mtr"),a.appendChild(this.children[f]._toMathML(b)),g.appendChild(a);d.appendChild(g);d.appendChild(this.mo(")"))}else if("matrix"==a.value){for(f=1;f<this.children.length;f++)if(this.children[f].type!==e.VECTOR)return d=document.createElement("mtext"),d.appendChild(document.createTextNode("???")),
d;d=document.createElement("mrow");d.appendChild(this.mo("("));g=document.createElement("mtable");for(f=1;f<this.children.length;f++){a=document.createElement("mtr");for(c=0;c<this.children[f].children.length;c++)a.appendChild(this.children[f].children[c]._toMathML(b));g.appendChild(a)}d.appendChild(g);d.appendChild(this.mo(")"))}else if("union"==a.value&&3==this.children.length){for(f=1;f<this.children.length;f++)if(this.children[f].type!==e.INTERVAL&&this.children[f].type!==e.SET)return d=document.createElement("mtext"),
d.appendChild(document.createTextNode("???")),d;d=document.createElement("mrow");d.appendChild(c._toMathML(b));d.appendChild(this.mo("\u222a"));d.appendChild(g._toMathML(b))}else if("intersection"==a.value&&3==this.children.length){for(f=1;f<this.children.length;f++)if(this.children[f].type!==e.INTERVAL&&this.children[f].type!==e.SET)return d=document.createElement("mtext"),d.appendChild(document.createTextNode("???")),d;d=document.createElement("mrow");d.appendChild(c._toMathML(b));d.appendChild(this.mo("\u2229"));
d.appendChild(g._toMathML(b))}else{d=document.createElement("mrow");d.appendChild(a._toMathML(b));d.appendChild(this.mo("("));for(f=1;f<this.children.length;f++)d.appendChild(this.children[f]._toMathML(b)),f<this.children.length-1&&d.appendChild(this.mo(l.ARG_SEPARATOR));d.appendChild(this.mo(")"))}return d;case e.VECTOR:m=!0;for(f=0;f<this.children.length;f++)this.children[f].type!==e.VECTOR&&(m=!1);d=document.createElement("mrow");d.appendChild(this.mo("("));g=document.createElement("mtable");for(f=
0;f<this.children.length;f++){a=document.createElement("mtr");if(m)for(c=0;c<this.children[f].children.length;c++)a.appendChild(this.children[f].children[c]._toMathML(b));else a.appendChild(this.children[f]._toMathML(b));g.appendChild(a)}d.appendChild(g);d.appendChild(this.mo(")"));return d;case e.INTERVAL:return d=document.createElement("mrow"),h=this.interval_type==e.OPEN_OPEN||this.interval_type==e.OPEN_CLOSED?this.mo("("):this.mo("["),d.appendChild(h),a=document.createElement("mrow"),a.appendChild(this.children[0]._toMathML(b)),
a.appendChild(this.mo(":")),a.appendChild(this.children[1]._toMathML(b)),d.appendChild(a),h=this.interval_type==e.OPEN_OPEN||this.interval_type==e.CLOSED_OPEN?this.mo(")"):this.mo("]"),d.appendChild(h),d;case e.SET:d=document.createElement("mrow");d.appendChild(this.mo("{"));a=document.createElement("mrow");for(f=0;f<this.children.length;f++)0<f&&a.appendChild(this.mo(";")),a.appendChild(this.children[f]._toMathML(b));d.appendChild(a);d.appendChild(this.mo("}"));return d;case e.SUBSCRIPT:g=document.createElement("msub");
g.appendChild(a._toMathML(b));if(2<this.children.length){d=document.createElement("mrow");for(f=1;f<this.children.length;f++)d.appendChild(this.children[f]._toMathML(b)),f<this.children.length-1&&d.appendChild(this.mo(l.ARG_SEPARATOR));g.appendChild(d)}else g.appendChild(c._toMathML(b));return g}};e.prototype.mi=function(b){var a=document.createElement("mi");e.symbols[b]&&(b=e.symbols[b]);a.appendChild(document.createTextNode(b));return a};e.prototype.mn=function(b){var a=document.createElement("mn");
a.appendChild(document.createTextNode(b));return a};e.prototype.mo=function(b){var a=document.createElement("mo");e.symbols[b]&&(b=e.symbols[b]);a.appendChild(document.createTextNode(b));return a};e.prototype.addP=function(b,a){var c,g;c=document.createElement("mrow");g=this.mo("(");var f;f=a.colors?a.colors:e.COLORS;g.setAttribute("mathcolor",f[a.depth%f.length]);c.appendChild(g);a.depth++;c.appendChild(b._toMathML(a));a.depth--;g=this.mo(")");g.setAttribute("mathcolor",f[a.depth%f.length]);c.appendChild(g);
return c};e.symbols={alpha:"\u03b1",beta:"\u03b2",gamma:"\u03b3",delta:"\u03b4",epsilon:"\u03b5",zeta:"\u03b6",eta:"\u03b7",theta:"\u03b8",iota:"\u03b9",kappa:"\u03ba",lambda:"\u03bb",mu:"\u03bc",nu:"\u03bd",xi:"\u03be",omicron:"\u03bf",pi:"\u03c0",rho:"\u03c1",sigma:"\u03c3",tau:"\u03c4",upsilon:"\u03c5",phi:"\u03c6",chi:"\u03c7",psi:"\u03c8",omega:"\u03c9",Alpha:"\u0391",Beta:"\u0392",Gamma:"\u0393",Delta:"\u0394",Epsilon:"\u0395",Zeta:"\u0396",Eta:"\u0397",Theta:"\u0398",Iota:"\u0399",Kappa:"\u039a",
Lambda:"\u039b",Mu:"\u039c",Nu:"\u039d",Xi:"\u039e",Omicron:"\u039f",Pi:"\u03a0",Rho:"\u03a1",Sigma:"\u03a3",Tau:"\u03a4",Upsilon:"\u03a5",Phi:"\u03a6",Chi:"\u03a7",Psi:"\u03a8",Omega:"\u03a9","#":"\u2260",">=":"\u2265","<=":"\u2264",inf:"\u221e",minf:"-\u221e",hbar:"\u210f",G:"\ud835\udca2"};"use strict";p.UNKNOWN=0;p.UNARY=1;p.BINARY=2;p.TERNARY=3;"use strict";n.prototype.toString=function(){return this.msg+" at "+this.from+" - "+this.to};"use strict";r.prototype.expression=function(b){var a,c=
this.current_token;if(null==c)throw new n("Expected something at the end",this.tokens[this.tokens.length-1].to+1);this.advance();if(null==c.op)a=new e(c.type,null,c.value,null);else{if(null==c.op.nud)throw new n("Unexpected operator '"+c.op.id+"'",c.from);a=c.op.nud(this)}for(;null!=this.current_token&&null!=this.current_token.op&&b<this.current_token.op.lbp;)c=this.current_token,this.advance(),a=c.op.led(this,a);return a};r.prototype.advance=function(b){if(b&&(null==this.current_token||null==this.current_token.op||
this.current_token.op.id!==b)){if(null==this.current_token)throw new n("Expected '"+b+"' at the end",this.tokens[this.tokens.length-1].to+1);throw new n("Expected '"+b+"'",this.current_token.from);}this.token_nr>=this.tokens.length?this.current_token=null:(this.current_token=this.tokens[this.token_nr],this.token_nr+=1)};r.prototype.addHiddenOperators=function(){for(var b=this.defs.findOperator("*"),a=this.defs.findOperator("`"),c=!1,g=!1,f=0;f<this.tokens.length-1;f++){var e=this.tokens[f],d=this.tokens[f+
1];this.unit_mode&&("`"==e.value?c=!0:c&&("^"==e.value?g=!0:g&&e.type==k.NUMBER?g=!1:g||e.type!=k.NUMBER?g||e.type!=k.OPERATOR||-1!="*/^()".indexOf(e.value)?e.type==k.NAME&&"("==d.value&&(c=!1):c=!1:c=!1));if(e.type==k.NAME&&d.type==k.NAME||e.type==k.NUMBER&&d.type==k.NAME||e.type==k.NUMBER&&d.type==k.NUMBER||e.type==k.NUMBER&&("("==d.value||"["==d.value||"{"==d.value)||(")"==e.value||"]"==e.value||"}"==e.value)&&d.type==k.NAME||(")"==e.value||"]"==e.value||"}"==e.value)&&d.type==k.NUMBER||(")"==
e.value||"]"==e.value||"}"==e.value)&&"("==d.value){if(e=this.unit_mode&&!c&&(e.type==k.NUMBER||")"==e.value||"]"==e.value||"}"==e.value)&&(d.type==k.NAME||("("==d.value||"["==d.value||"{"==d.value)&&this.tokens.length>f+2&&this.tokens[f+2].type==k.NAME)){var h,l;d.type==k.NAME?(h=d,l=f+1):(l=f+2,h=this.tokens[l]);for(var s=0;s<this.constants.length;s++)if(h.value==this.constants[s]){e=!1;break}if(this.tokens.length>l+1&&"("==this.tokens[l+1].value)for(l="pow sqrt abs exp factorial diff integrate sum product limit binomial matrix ln log log10 mod signum ceiling floor sin cos tan asin acos atan atan2 sinh cosh tanh asinh acosh atanh".split(" "),
s=0;s<l.length;s++)if(h.value==l[s]){e=!1;break}}e?(d=new k(k.OPERATOR,d.from,d.from,a.id,a),c=!0):d=new k(k.OPERATOR,d.from,d.from,b.id,b);this.tokens.splice(f+1,0,d)}}};r.prototype.parse=function(b){this.tokens=(new t(this.defs,b)).tokenize();if(0==this.tokens.length)return null;this.implicit_operators&&this.addHiddenOperators();this.token_nr=0;this.current_token=this.tokens[this.token_nr];this.advance();b=this.expression(0);if(null!=this.current_token)throw new n("Expected the end",this.current_token.from);
return b};"use strict";t.prototype.tokenize=function(){var b,a,c,g,f;a=0;b=this.text.charAt(a);f=[];a:for(;b;)if(g=a," ">=b)a++,b=this.text.charAt(a);else{if("0"<=b&&"9">=b||(b===l.DECIMAL_SIGN_1||b===l.DECIMAL_SIGN_2)&&"0"<=this.text.charAt(a+1)&&"9">=this.text.charAt(a+1)){c="";if(b!==l.DECIMAL_SIGN_1&&b!==l.DECIMAL_SIGN_2)for(a++,c+=b;;){b=this.text.charAt(a);if("0">b||"9"<b)break;a++;c+=b}if(b===l.DECIMAL_SIGN_1||b===l.DECIMAL_SIGN_2)for(a++,c+=b;;){b=this.text.charAt(a);if("0">b||"9"<b)break;
a+=1;c+=b}if("e"===b||"E"===b){a++;c+=b;b=this.text.charAt(a);if("-"===b||"+"===b)a++,c+=b,b=this.text.charAt(a);if("0">b||"9"<b)throw new n("syntax error in number exponent",g,a);do a++,c+=b,b=this.text.charAt(a);while("0"<=b&&"9">=b)}var e=+c.replace(l.DECIMAL_SIGN_1,".").replace(l.DECIMAL_SIGN_2,".");if(isFinite(e)){f.push(new k(k.NUMBER,g,a-1,c,null));continue}else throw new n("syntax error in number",g,a);}for(c=0;c<this.defs.operators.length;c++)if(e=this.defs.operators[c],this.text.substring(a,
a+e.id.length)===e.id){a+=e.id.length;b=this.text.charAt(a);f.push(new k(k.OPERATOR,g,a-1,e.id,e));continue a}if("a"<=b&&"z">=b||"A"<=b&&"Z">=b||"\u03b1"<=b&&"\u03c9">=b||"\u0391"<=b&&"\u03a9">=b||"\u00b5"==b){c=b;for(a++;;)if(b=this.text.charAt(a),"a"<=b&&"z">=b||"A"<=b&&"Z">=b||"\u03b1"<=b&&"\u03c9">=b||"\u0391"<=b&&"\u03a9">=b||"\u00b5"==b||"0"<=b&&"9">=b||"_"===b)c+=b,a++;else break;f.push(new k(k.NAME,g,a-1,c,null))}else throw new n("unrecognized operator",g,a);}return f};"use strict";k.UNKNOWN=
0;k.NAME=1;k.NUMBER=2;k.OPERATOR=3;"use strict";var u=function(b){var a,c,e,f,m;a=b.ta;c=b.output_node;a=a.value;f="";for(m=a;m!=f;)f=m,m=f.replace(/\[[^\[\]]*\]/g,"");if(m.split("[").length==m.split("]").length){for(f="";m!=f;)f=m,m=f.replace(/\([^\(\)]*\)/g,"");if(m.split("(").length==m.split(")").length){for(f="";m!=f;)f=m,m=f.replace(/\{[^\{\}]*\}/g,"");m.split("{").length==m.split("}").length&&-1!=m.indexOf(l.ARG_SEPARATOR)&&(a="["+a+"]")}}if(a!=b.oldtxt){for(b.oldtxt=a;null!=c.firstChild;)c.removeChild(c.firstChild);
c.removeAttribute("title");if(""!=a){b=b.parser;try{if(e=b.parse(a),null!=e){var d=document.createElement("math");d.setAttribute("display","block");d.appendChild(e.toMathML());c.appendChild(d);MathJax.Hub.Queue(["Typeset",MathJax.Hub,c])}}catch(h){e="error: "+h,c.setAttribute("title",e),h instanceof n?(c.appendChild(document.createTextNode(a.substring(0,h.from))),e=document.createElement("span"),e.appendChild(document.createTextNode(a.substring(h.from,h.to+1))),e.style.border="solid 1px red",e.style.minWidth=
"1px",c.appendChild(e),h.to<a.length-1&&c.appendChild(document.createTextNode(a.substring(h.to+1)))):(a=document.createTextNode(e),c.appendChild(a))}}}},q=[];return{Definitions:l,ENode:e,Operator:p,ParseException:n,Parser:r,initEditors:function(){MathJax.Hub.Config({messageStyle:"none"});for(var b=document.getElementsByClassName("math"),a=0;a<q.length;a++){for(var c=q[a].ta,e=!1,f=0;f<b.length;f++)if(b[f]==c){e=!0;break}e||(f=q[a].output_node,f.parentNode&&f.parentNode.removeChild(f),q.splice(a,1),
a--)}for(a=0;a<b.length;a++)if(c=b[a],"TEXTAREA"==c.nodeName||"INPUT"==c.nodeName){e=-1;for(f=0;f<q.length;f++)if(q[f].ta==c){e=f;break}var m="true"===c.getAttribute("data-implicit_operators"),d="true"===c.getAttribute("data-unit_mode"),h=c.getAttribute("data-constants");h&&(h=h.split(/[\s,]+/));if(-1==e){f=document.createElement("span");f.style.display="none";f.style.position="absolute";f.style.backgroundColor="rgba(255,255,224,0.9)";f.style.color="black";f.style.border="1px solid #A0A0A0";f.style.padding=
"5px";f.style.zIndex="1";var l=function(a,b){var c,d=a.getBoundingClientRect();c=a;for(var e=0,f=0;c&&!isNaN(c.offsetLeft)&&!isNaN(c.offsetTop);)if(e+=c.offsetLeft-c.scrollLeft,f+=c.offsetTop-c.scrollTop,c=c.offsetParent){var g=window.getComputedStyle(c);if("absolute"==g.position||"relative"==g.position)break}c=f;b.style.left=e+"px";b.style.top=window.innerHeight>d.bottom+b.offsetHeight?c+a.offsetHeight+"px":c-b.offsetHeight+"px"};c.nextSibling?c.parentNode.insertBefore(f,c.nextSibling):c.parentNode.appendChild(f);
c.addEventListener("blur",function(a){return function(b){a.style.display="none"}}(f),!1);c.addEventListener("focus",function(a,b){return function(c){""!=a.value&&(b.style.display="block",l(a,b))}}(c,f),!1);f.addEventListener("mouseenter",function(a){return function(b){a.hasAttribute("title")||(a.style.display="none")}}(f),!1);e=q.length;q[e]={ta:c,output_node:f,oldtxt:"",parser:new r(m,d,h)};f=function(a){return function(b){var c=q[a];u(c);document.activeElement==c.ta&&(""!=c.ta.value?(c.output_node.style.display=
"block",MathJax.Hub.Queue(function(){l(c.ta,c.output_node)})):c.output_node.style.display="none")}}(e);""!=c.value&&f();c.addEventListener("change",f,!1);c.addEventListener("keyup",f,!1)}else{var k,n=q[e].parser;if(h||0!=n.constants.length)if(h){if(k=n.constants.length==h.length)for(f=0;f<h.length;f++)if(n.constants[f]!=h[f]){k=!1;break}}else k=!1;else k=!0;n.implicit_operators==m&&n.unit_mode==d&&k||(q[e].parser=new r(m,d,h),""!=c.value&&(q[e].oldtxt="",u(q[e])))}}},updateMathSpanAndDiv:function(){for(var b=
document.getElementsByClassName("math"),a=[],c=0,e=a.length=b.length;c<e;c++)a[c]=b[c];for(c=0;c<a.length;c++)if(b=a[c],("SPAN"==b.nodeName||"DIV"==b.nodeName)&&null!=b.firstChild&&3==b.firstChild.nodeType){var e="SPAN"==b.nodeName,f=b.firstChild.nodeValue,m="true"===b.getAttribute("data-implicit_operators"),d="true"===b.getAttribute("data-unit_mode"),h=b.getAttribute("data-constants");h&&(h=h.split(/[\s,]+/));m=new r(m,d,h);try{var l=m.parse(f);if(null!=l){var k=document.createElement("math");k.setAttribute("display",
e?"inline":"block");k.appendChild(l.toMathML(["#000000"]));b.classList.remove("math");b.removeChild(b.firstChild);b.appendChild(k);MathJax.Hub.Queue(["Typeset",MathJax.Hub,b])}}catch(n){b.firstChild.nodeValue="[syntax error in math:"+n+"]",b.classList.remove("math")}}}}}();

Index: modules/damieng/graphical_editor/loncapa_daxe/web/LocalStrings_en.properties
+++ modules/damieng/graphical_editor/loncapa_daxe/web/LocalStrings_en.properties
# Toolbar insert buttons
tex_equation = TeX equation

# Toolbar Section menu
Section = Section
introduction = introduction
conclusion = conclusion
prerequisites = prerequisites
objectives = objectives
reminder = reminder
definition = definition
demonstration = demonstration
example = example
advise = advise
remark = remark
warning = warning
more_information = more information
method = method
activity = activity
bibliography = bibliography
citation = citation

# Templates menu
Templates = Templates

# LCDBlock
lcdblock_editable = Element view with editable attributes
lcdblock_normal = Normal element view
lcdblock_collapsed = Collapsed element view
advanced = advanced
simple = simple

# Lm
lm_symbols = symbols
lm_units = units

# LCDParameter
template = Template

# SimpleHintgroup
hint = Hint:

# RadioFoilgroup
foil_selection = Foil selection
insert_foil = Insert a foil
remove_foil = Remove this foil

# RadioFoil
click_to_select = Click on a radio button to select the correct answer

# OptionFoilgroup
possible_options = Possible options:
add_option = Add an option
foils = Foils:
add_foil = Add a foil
options_change = Change to the options

# OptionFoil
foil_move = Foil move

Index: modules/damieng/graphical_editor/loncapa_daxe/web/LocalStrings_fr.properties
+++ modules/damieng/graphical_editor/loncapa_daxe/web/LocalStrings_fr.properties
# Toolbar insert buttons
tex_equation = équation TeX

# Toolbar Section menu
Section = Section
introduction = introduction
conclusion = conclusion
prerequisites = prérequis
objectives = objectifs
reminder = rappel
definition = définition
demonstration = démonstration
example = exemple
advise = conseil
remark = remarque
warning = attention
more_information = en savoir plus
method = méthode
activity = activité
bibliography = bibliographie
citation = citation

# Templates menu
Templates = Modèles

# LCDBlock
lcdblock_editable = Vue d'élément avec des attributs éditables
lcdblock_normal = Vue d'élément normale
lcdblock_collapsed = Vue d'élément réduite
advanced = avancé
simple = simplifié

# Lm
lm_symbols = symboles
lm_units = unités

# LCDParameter
template = Modèle

# SimpleHintgroup
hint = Aide:

# RadioFoilgroup
foil_selection = Sélection de leurre
insert_foil = Insérer un leurre
remove_foil = Supprimer ce leurre

# RadioFoil
click_to_select = Cliquer sur un bouton radio pour sélectionner la bonne réponse

# OptionFoilgroup
possible_options = Options possibles:
add_option = Ajouter une option
foils = Leurres:
add_foil = Ajouter un leurre
options_change = Changement des options

# OptionFoil
foil_move = Déplacement de leurre

Index: modules/damieng/graphical_editor/loncapa_daxe/web/lcd_button.dart
+++ modules/damieng/graphical_editor/loncapa_daxe/web/lcd_button.dart
/*
  This file is part of LONCAPA-Daxe.

  LONCAPA-Daxe 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 3 of the License, or
  (at your option) any later version.

  LONCAPA-Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of loncapa_daxe;

class LCDButton {
  static int idcount = 0;
  String _title;
  String iconFilename;
  ActionFunction action;
  String id;
  bool enabled;
  bool selected;
  StreamSubscription<h.MouseEvent> listener;
  
  LCDButton(this._title, this.iconFilename, this.action, {this.enabled:true, this.selected:false}) {
    id = "lcdbutton_$idcount";
    idcount++;
  }
  
  h.Element html() {
    h.DivElement div = new h.DivElement();
    div.id = id;
    div.classes.add('lcdbutton');
    if (!enabled)
      div.classes.add('lcdbutton-disabled');
    if (selected)
      div.classes.add('lcdbutton-selected');
    div.setAttribute('title', _title);
    h.ImageElement img = new h.ImageElement();
    img.setAttribute('src', 'images/' + iconFilename);
    if (enabled)
      listener = div.onClick.listen((h.MouseEvent event) => action());
    div.append(img);
    return(div);
  }
  
  String get title {
    return(_title);
  }
  
  void set title(String t) {
    _title = t;
    h.Element div = getHTMLNode();
    div.setAttribute('title', _title);
  }
  
  h.Element getHTMLNode() {
    return(h.querySelector("#$id"));
  }
  
  void disable() {
    if (!enabled)
      return;
    enabled = false;
    h.Element div = getHTMLNode();
    div.classes.add('lcdbutton-disabled');
    listener.cancel();
  }
  
  void enable() {
    if (enabled)
      return;
    enabled = true;
    h.Element div = getHTMLNode();
    div.classes.remove('lcdbutton-disabled');
    listener = div.onClick.listen((h.MouseEvent event) => action());
  }
  
  void select() {
    if (selected)
      return;
    selected = true;
    h.Element div = getHTMLNode();
    div.classes.add('lcdbutton-selected');
  }
  
  void deselect() {
    if (!selected)
      return;
    selected = false;
    h.Element div = getHTMLNode();
    div.classes.remove('lcdbutton-selected');
  }
  
}

Index: modules/damieng/graphical_editor/loncapa_daxe/web/lcd_strings.dart
+++ modules/damieng/graphical_editor/loncapa_daxe/web/lcd_strings.dart
/*
  This file is part of LONCAPA-Daxe.

  LONCAPA-Daxe 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 3 of the License, or
  (at your option) any later version.

  LONCAPA-Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

/// Provides localized strings.
library LCDStrings;

import 'dart:async';
import 'dart:collection';
import 'dart:html' as h;
import 'package:intl/intl_browser.dart'; // or intl-standalone (see findSystemLocale)


/**
 * Provides localized strings read from properties files.
 * The current language file is read at application loading time.
 */
class LCDStrings {
  
  static String resourcePath = "LocalStrings";
  static HashMap<String, String> map = null;
  static String systemLocale;
  static const defaultLocale = 'en';
  
  static Future<bool> load() {
    Completer<bool> completer = new Completer<bool>();
    findSystemLocale().then((String sl) {
      // note: this is not always the language chosen by the user
      // see https://code.google.com/p/chromium/issues/detail?id=101138
      if (sl != null)
        systemLocale = sl;
      else
        systemLocale = defaultLocale;
      String language = systemLocale.split('_')[0];
      String fullFilePath = "${resourcePath}_$language.properties";
      
      h.HttpRequest request = new h.HttpRequest();
      request.open("GET", fullFilePath);
      request.onLoad.listen((h.ProgressEvent event) {
        String txt = request.responseText;
        map = new HashMap<String, String>();
        List<String> lines = txt.split("\n");
        for (String line in lines) {
          if (line.startsWith('#'))
            continue;
          int ind = line.indexOf('=');
          if (ind == -1)
            continue;
          String key = line.substring(0, ind).trim();
          String value = line.substring(ind + 1).trim();
          map[key] = value;
        }
        completer.complete(true);
      });
      request.onError.listen((h.ProgressEvent event) {
        completer.completeError("Error when reading the strings in $resourcePath");
      });
      request.send();
    });
    return(completer.future);
  }
  
  static String get(String key) {
    return(map[key]);
  }
  
}

Index: modules/damieng/graphical_editor/loncapa_daxe/web/loncapa_daxe.css
+++ modules/damieng/graphical_editor/loncapa_daxe/web/loncapa_daxe.css
span.empty_tag, span.start_tag, span.end_tag, span.simple_type {
  background-color: #F6F6F6;
  border: 1px solid #808080;
}

lcdblock {
  border-top: 1px solid #AAA;
  border-left: 1px solid #AAA;
  border-bottom: 1px solid #AAA;
  box-shadow: 0px 1px 1px #BBB;
  margin-top: 8px;
  margin-bottom: 8px;
  border-top-left-radius: 0.4em;
  border-top-right-radius: 0.4em;
  border-bottom-left-radius: 0.4em;
}

lcdblock.invalid {
  border-top: 1px solid #F00;
  border-left: 1px solid #F00;
  border-bottom: 1px solid #F00;
}

selected .lcdblock-header {
  background-color: #50A0FF;
}

lcdblock-header {
  background-color: #F5F5F5;
  padding: 2px;
  border-top-left-radius: 0.4em;
  border-top-right-radius: 0.4em;
  box-shadow: 0px 1px 2px #AAA;
  cursor: default;
  font-family: sans-serif;
  font-weight: normal;
  font-style: normal;
  font-size: medium;
  color: black;
  text-align: left;
}

lcdblock-header:after {
  /* clearfix for floating buttons */
  /* we can't use overflow:auto because of the parameter template menu which needs to overflow */
  visibility: hidden;
  display: block;
  content: "";
  clear: both;
  height: 0;
}

lcdblock-header.without-content-afterwards {
  border-bottom-left-radius: 0.4em;
}

lcdblock.invalid>.lcdblock-header {
  background-color: #FFE0B0;
}

lcdblock.invalid.selected>.lcdblock-header {
  background-color: #50A0FF;
}

selected .lcdblock.invalid>.lcdblock-header {
  background-color: #50A0FF;
}

lcdblock-title {
  margin-left: 4px;
  margin-right: 3px;
  font-style: italic;
}

lcdblock-attributes {
  font-size: 90%;
}

lcdblock-content {
  margin-top: 3px;
}

lcd-button-box {
  display: inline-block;
  line-height: 20px;
  border: 1px solid #AAA;
  margin: 0.2em;
  background: #FAFAFA;
  border-radius: 5px;
  overflow: hidden;
  cursor: default;
}

lcdbutton {
  display: inline-block;
  line-height: 18px;
  padding: 3px;
  cursor: default;
}

lcdbutton-disabled {
  opacity: 0.3;
}

lcdbutton-selected {
  background-color: #DDD;
}

lcdbutton:hover {
  background-color: #EEE;
}

lcdbutton-disabled:hover {
  background-color: transparent;
}

lcdbutton-disabled:hover {
  background-color: transparent;
}

lcdbutton img {
  vertical-align: middle;
}

lcdblock .lcd-button-box {
  float: right;
}

lcdblock .lcd-advanced {
  float: right;
}

tex {
  cursor: default;
}

hr {
  cursor: default;
  margin: 0px;
  padding-top: 0.5em;
  padding-bottom: 0.5em;
}

input.math {
  font-family: monospace;
}

perl-text {
  visibility: hidden;
}

perl-text .selection {
  visibility: visible;
}

selected .perl-text {
  visibility: visible;
}

perl-colored {
  z-index: -1;
  visibility: visible;
  font-family: monospace;
}

perl-colored .keyword {
  font-weight: bold;
}
perl-colored .string {
  color: #050;
}
perl-colored .comment {
  color: #666;
}
perl-colored .variable {
  color: #049;
}
perl-colored .number {
  color: #640;
}
perl-colored .function-call {
  color: #705;
}

radio-foilgroup {
  white-space: normal; /* to prevent a bug with draggable in Firefox */
  margin-bottom: 1em;
}

radio-foilgroup>table {
  padding-left: 1em;
  padding-right: 1em;
}

radio-foilgroup>table>tr>td:first-child {
  vertical-align: middle;
}

radio-foilgroup>table>tr>td:nth-child(2) {
  min-width: 5em;
}

radio-foilgroup>table>tr>td {
  position: relative; /* bug workaround for Chromium (without this the tr drag&drop ghost is not visible) */
}

radio-foilgroup>table>tr:hover .delete-icon {
  visibility: visible;
}

radio-foilgroup>table>tr>td {
  border-top: 1px solid #FFF;
  border-bottom: 1px solid #FFF;
}

radio-foilgroup>table>tr.dragbefore>td {
  border-top: 1px dashed #000;
}

radio-foilgroup>table>tr.dragafter>td {
  border-bottom: 1px dashed #000;
}

grip-icon {
  vertical-align: middle;
  padding-right: 6px;
  padding-left: 4px;
  cursor: move;
}

optionresponse-options {
  margin-bottom: 1em;
}

option-list {
  list-style: none;
  margin-top: 0.5em;
  margin-bottom: 0.5em;
  white-space: normal; /* to prevent a bug with draggable in Firefox */
  cursor: default;
}

option-list {
  margin-left: 0;
  padding-left: 1em;
}

option-list>li {
  border-top: 1px solid #FFF;
  border-bottom: 1px solid #FFF;
}

option-list>li.dragbefore {
  border-top: 1px dashed #000;
}

option-list>li.dragafter {
  border-bottom: 1px dashed #000;
}

delete-icon {
  visibility: hidden;
  cursor: default;
}

option-list>li:hover .delete-icon {
  visibility: visible;
}

optionresponse-foilgroup {
  margin-bottom: 1em;
  white-space: normal;
}

optionresponse-foilgroup>table {
  padding-left: 1em;
}

optionresponse-foilgroup>table>tr>td:first-child {
  vertical-align: middle;
}

optionresponse-foilgroup>table>tr>td:nth-child(2) {
  min-width: 5em;
}

optionresponse-foilgroup>table>tr>td {
  position: relative; /* bug workaround for Chromium (without this the tr drag&drop ghost is not visible) */
}

optionresponse-foilgroup>table>tr:hover .delete-icon {
  visibility: visible;
}

optionresponse-foilgroup>table>tr>td {
  border-top: 1px solid #FFF;
  border-bottom: 1px solid #FFF;
}

optionresponse-foilgroup>table>tr.dragbefore>td {
  border-top: 1px dashed #000;
}

optionresponse-foilgroup>table>tr.dragafter>td {
  border-bottom: 1px dashed #000;
}

rank-foilgroup {
  white-space: normal; /* to prevent a bug with draggable in Firefox */
  margin-bottom: 1em;
}

rank-foilgroup>table {
  padding-left: 1em;
  padding-right: 1em;
}

rank-foilgroup>table>tr>td:first-child {
  vertical-align: middle;
}

rank-foilgroup>table>tr>td:nth-child(2) {
  min-width: 5em;
}

rank-foilgroup>table>tr>td {
  position: relative; /* bug workaround for Chromium (without this the tr drag&drop ghost is not visible) */
}

rank-foilgroup>table>tr:hover .delete-icon {
  visibility: visible;
}

rank-foilgroup>table>tr>td {
  border-top: 1px solid #FFF;
  border-bottom: 1px solid #FFF;
}

rank-foilgroup>table>tr.dragbefore>td {
  border-top: 1px dashed #000;
}

rank-foilgroup>table>tr.dragafter>td {
  border-bottom: 1px dashed #000;
}

rank-number {
  border: 1px solid #555;
  padding: 1px;
  margin: 1px;
}

Index: modules/damieng/graphical_editor/loncapa_daxe/web/loncapa_daxe.dart
+++ modules/damieng/graphical_editor/loncapa_daxe/web/loncapa_daxe.dart
/*
  This file is part of LON-CAPA.

  LON-CAPA 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 3 of the License, or
  (at your option) any later version.

  LON-CAPA 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 LON-CAPA.  If not, see <http://www.gnu.org/licenses/>.
*/

library loncapa_daxe;

import 'dart:async';
import 'dart:collection';
import 'dart:html' as h;
import 'package:daxe/daxe.dart';
import 'package:daxe/src/xmldom/xmldom.dart' as x;
import 'package:daxe/src/strings.dart';
import 'package:daxe/src/nodes/nodes.dart' show DNCData, DNText, SimpleTypeControl, ParentUpdatingDNText;
import 'dart:js' as js;

import 'lcd_strings.dart';
part 'nodes/lcd_block.dart';
part 'nodes/lcd_parameter.dart';
part 'nodes/tex_mathjax.dart';
part 'nodes/lm.dart';
part 'nodes/radio_response.dart';
part 'nodes/radio_foilgroup.dart';
part 'nodes/radio_foil.dart';
part 'nodes/option_response.dart';
part 'nodes/option_foilgroup.dart';
part 'nodes/option_foil.dart';
part 'nodes/rank_response.dart';
part 'nodes/rank_foilgroup.dart';
part 'nodes/rank_foil.dart';
part 'nodes/hintgroup.dart';
part 'nodes/simple_ui_text.dart';
part 'nodes/script_block.dart';
part 'lcd_button.dart';


void main() {
  NodeFactory.addCoreDisplayTypes();
  
  addDisplayType('lcdblock',
        (x.Element ref) => new LCDBlock.fromRef(ref),
        (x.Node node, DaxeNode parent) => new LCDBlock.fromNode(node, parent)
    );
  
  addDisplayType('texmathjax',
        (x.Element ref) => new TeXMathJax.fromRef(ref),
        (x.Node node, DaxeNode parent) => new TeXMathJax.fromNode(node, parent)
    );
  
  addDisplayType('lm',
        (x.Element ref) => new Lm.fromRef(ref),
        (x.Node node, DaxeNode parent) => new Lm.fromNode(node, parent)
    );
  
  addDisplayType('script',
        (x.Element ref) => new ScriptBlock.fromRef(ref),
        (x.Node node, DaxeNode parent) => new ScriptBlock.fromNode(node, parent)
    );
  
  addDisplayType('parameter',
        (x.Element ref) => new LCDParameter.fromRef(ref),
        (x.Node node, DaxeNode parent) => new LCDParameter.fromNode(node, parent)
    );
  
  addDisplayType('radioresponse',
        (x.Element ref) => new RadioResponse.fromRef(ref),
        (x.Node node, DaxeNode parent) => new RadioResponse.fromNode(node, parent)
    );
  
  addDisplayType('optionresponse',
        (x.Element ref) => new OptionResponse.fromRef(ref),
        (x.Node node, DaxeNode parent) => new OptionResponse.fromNode(node, parent)
    );
  
  addDisplayType('rankresponse',
        (x.Element ref) => new RankResponse.fromRef(ref),
        (x.Node node, DaxeNode parent) => new RankResponse.fromNode(node, parent)
    );
  
  addDisplayType('foilgroup',
        (x.Element ref) {
          if (ref.getAttribute('type') == 'radiobuttonresponse--foilgroup')
            return new RadioFoilgroup.fromRef(ref);
          else if (ref.getAttribute('type') == 'optionresponse--foilgroup')
            return new OptionFoilgroup.fromRef(ref);
          else if (ref.getAttribute('type') == 'rankresponse--foilgroup')
            return new RankFoilgroup.fromRef(ref);
          return new LCDBlock.fromRef(ref);
        },
        (x.Node node, DaxeNode parent) {
          if (parent is RadioResponse)
            return new RadioFoilgroup.fromNode(node, parent);
          else if (parent is OptionResponse)
            return new OptionFoilgroup.fromNode(node, parent);
          else if (parent is RankResponse)
            return new RankFoilgroup.fromNode(node, parent);
          return new LCDBlock.fromNode(node, parent);
        }
    );
  
  addDisplayType('foil',
        (x.Element ref) {
          if (ref.getAttribute('type') == 'radiobuttonresponse--foil')
            return new RadioFoil.fromRef(ref);
          else if (ref.getAttribute('type') == 'optionresponse--foil')
            return new OptionFoil.fromRef(ref);
          else if (ref.getAttribute('type') == 'rankresponse--foil')
            return new RankFoil.fromRef(ref);
          return new LCDBlock.fromRef(ref);
        },
        (x.Node node, DaxeNode parent) {
          if (parent is RadioFoilgroup)
            return new RadioFoil.fromNode(node, parent);
          else if (parent is OptionFoilgroup)
            return new OptionFoil.fromNode(node, parent);
          else if (parent is RankFoilgroup)
            return new RankFoil.fromNode(node, parent);
          return new LCDBlock.fromNode(node, parent);
        }
    );
  
  addDisplayType('hintgroup',
        (x.Element ref) => new Hintgroup.fromRef(ref),
        (x.Node node, DaxeNode parent) => new Hintgroup.fromNode(node, parent)
    );
  
  Future.wait([Strings.load(), LCDStrings.load(), _readTemplates('templates.xml')]).then((List responses) {
    _init_daxe().then((v) {
      // add things to the toolbar
      ToolbarMenu sectionMenu = _makeSectionMenu();
      if (sectionMenu != null)
        page.toolbar.add(sectionMenu);
      x.Element texRef = doc.cfg.elementReference('m');
      if (texRef != null) {
        ToolbarBox insertBox = new ToolbarBox();
        ToolbarButton texButton = new ToolbarButton(
            LCDStrings.get('tex_equation'), 'images/tex.png',
            () => doc.insertNewNode(texRef, 'element'), Toolbar.insertButtonUpdate, 
            data:new ToolbarStyleInfo([texRef], null, null));
        insertBox.add(texButton);
        page.toolbar.add(insertBox);
      }
      h.Element tbh = h.querySelector('.toolbar');
      tbh.replaceWith(page.toolbar.html());
      page.adjustPositionsUnderToolbar();
      page.updateAfterPathChange();
      // add things to the menubar
      if (responses[2] is x.Document) {
        // at this point the menubar html is already in the document, so we have to fix the HTML
        h.Element menubarDiv = h.document.getElementsByClassName('menubar')[0];
        if (doc.filePath.indexOf('&url=') != -1) { // otherwise we are not on LON-CAPA
          MenuItem item = new MenuItem(Strings.get('menu.save'), () => save(), shortcut: 'S');
          Menu fileMenu = page.mbar.menus[0];
          fileMenu.add(item);
          menubarDiv.firstChild.replaceWith(page.mbar.createMenuDiv(fileMenu));
        }
        Menu m = _makeTemplatesMenu(responses[2]);
        page.mbar.add(m);
        menubarDiv.append(page.mbar.createMenuDiv(m));
        page.updateAfterPathChange();
      } else
        print("Error reading templates file, could not build the menu.");
    });
  });
}

Future _init_daxe() {
  Completer completer = new Completer();
  doc = new DaxeDocument();
  page = new WebPage();
  
  // check parameters for a config and file to open
  String file = null;
  String config = null;
  String saveURL = null;
  h.Location location = h.window.location;
  String search = location.search;
  if (search.startsWith('?'))
    search = search.substring(1);
  List<String> parameters = search.split('&');
  for (String param in parameters) {
    List<String> lparam = param.split('=');
    if (lparam.length != 2)
      continue;
    if (lparam[0] == 'config')
      config = lparam[1];
    else if (lparam[0] == 'file')
      file = Uri.decodeComponent(lparam[1]);
    else if (lparam[0] == 'save')
      saveURL = lparam[1];
  }
  if (saveURL != null)
    doc.saveURL = saveURL;
  if (config != null && file != null)
    page.openDocument(file, config).then((v) => completer.complete());
  else if (config != null)
    page.newDocument(config).then((v) => completer.complete());
  else {
    h.window.alert(Strings.get('daxe.missing_config'));
    completer.completeError(Strings.get('daxe.missing_config'));
  }
  return(completer.future);
}

void save() {
  saveOnLONCAPA().then((_) {
    h.window.alert(Strings.get('save.success'));
  }, onError: (DaxeException ex) {
    h.window.alert(Strings.get('save.error') + ': ' + ex.message);
  });
}

/**
 * Send the document with a POST request to LON-CAPA.
 */
Future saveOnLONCAPA() {
  int ind = doc.filePath.indexOf('&url=');
  if (ind == -1)
    return(new Future.error(new DaxeException('bad URL')));
  String path = doc.filePath.substring(ind+5);
  path = Uri.decodeQueryComponent(path);
  ind = path.lastIndexOf('/');
  String filename;
  if (ind == -1)
    filename = path;
  else {
    filename = path.substring(ind+1);
    path = path.substring(0, ind+1);
  }
  Completer completer = new Completer();
  String bound = 'AaB03x';
  h.HttpRequest request = new h.HttpRequest();
  request.onLoad.listen((h.ProgressEvent event) {
    completer.complete(); // TODO: check for something, status is sometimes wrongly OK
  });
  request.onError.listen((h.ProgressEvent event) {
    completer.completeError(new DaxeException(request.status.toString()));
  });
  request.open('POST', '/upload_file');
  request.setRequestHeader('Content-Type', "multipart/form-data; boundary=$bound");
  
  StringBuffer sb = new StringBuffer();
  sb.write("--$bound\r\n");
  sb.write('Content-Disposition: form-data; name="uploads_path"\r\n');
  sb.write('Content-type: text/plain; charset=UTF-8\r\n');
  sb.write('Content-transfer-encoding: 8bit\r\n\r\n');
  sb.write(path);
  sb.write("\r\n--$bound\r\n");
  sb.write('Content-Disposition: form-data; name="uploads"; filename="$filename"\r\n');
  sb.write('Content-Type: application/octet-stream\r\n\r\n');
  doc.dndoc.xmlEncoding = 'UTF-8'; // the document is forced to use UTF-8
  sb.write(doc.toString());
  sb.write('\r\n--$bound--\r\n\r\n');
  request.send(sb.toString());
  return(completer.future);
}

ToolbarMenu _makeSectionMenu() {
  Menu menu = new Menu(LCDStrings.get('Section'));
  List<x.Element> sectionRefs = doc.cfg.elementReferences('section');
  if (sectionRefs == null || sectionRefs.length == 0)
    return(null);
  x.Element h1Ref = doc.cfg.elementReference('h1');
  for (String role in ['introduction', 'conclusion', 'prerequisites', 'objectives',
                       'reminder', 'definition', 'demonstration', 'example', 'advise',
                       'remark', 'warning', 'more_information', 'method',
                       'activity', 'bibliography', 'citation']) {
    MenuItem menuItem = new MenuItem(LCDStrings.get(role), null,
        data:new ToolbarStyleInfo(sectionRefs, null, null));
    menuItem.action = () {
      ToolbarStyleInfo info = menuItem.data;
      x.Element sectionRef = info.validRef;
      LCDBlock section = NodeFactory.create(sectionRef);
      section.state = 1;
      section.setAttribute('class', 'role-' + role);
      LCDBlock h1 = NodeFactory.create(h1Ref);
      h1.state = 1;
      if (doc.insert2(section, page.getSelectionStart())) {
        doc.insertNode(h1, new Position(section, 0));
        page.cursor.moveTo(new Position(h1, 0));
        page.updateAfterPathChange();
      }
    };
    menu.add(menuItem);
  }
  ToolbarMenu tbmenu = new ToolbarMenu(menu, Toolbar.insertMenuUpdate, page.toolbar);
  return(tbmenu);
}

Future<x.Document> _readTemplates(String templatesPath) {
  x.DOMParser dp = new x.DOMParser();
  return(dp.parseFromURL(templatesPath));
}

Menu _makeTemplatesMenu(x.Document templatesDoc) {
  Menu menu = new Menu(LCDStrings.get('Templates'));
  x.Element templates = templatesDoc.documentElement;
  for (x.Node child in templates.childNodes) {
    if (child.nodeType == x.Node.ELEMENT_NODE && child.nodeName == 'menu') {
      menu.add(_makeMenu(child));
    }
  }
  return(menu);
}

Menu _makeMenu(x.Element el) {
  String locale = LCDStrings.systemLocale;
  String defaultLocale = LCDStrings.defaultLocale;
  String title;
  for (x.Node child in el.childNodes) {
    if (child.nodeType == x.Node.ELEMENT_NODE && child.nodeName == 'title') {
      if (child.firstChild != null && child.firstChild.nodeType == x.Node.TEXT_NODE) {
        if ((child as x.Element).getAttribute('lang') == locale) {
          title = child.firstChild.nodeValue;
          break;
        } else if ((child as x.Element).getAttribute('lang') == defaultLocale) {
          title = child.firstChild.nodeValue;
        }
      }
    }
  }
  if (title == null)
    title = '?';
  Menu menu = new Menu(title);
  for (x.Node child in el.childNodes) {
    if (child.nodeType == x.Node.ELEMENT_NODE) {
      if (child.nodeName == 'menu') {
        menu.add(_makeMenu(child));
      } else if (child.nodeName == 'item') {
        menu.add(_makeItem(child));
      }
    }
  }
  return(menu);
}

MenuItem _makeItem(x.Element item) {
  String locale = LCDStrings.systemLocale;
  String defaultLocale = LCDStrings.defaultLocale;
  String path, type, title, help;
  for (x.Node child in item.childNodes) {
    if (child.nodeType == x.Node.ELEMENT_NODE) {
      if (child.nodeName == 'title') {
        if (child.firstChild != null && child.firstChild.nodeType == x.Node.TEXT_NODE) {
          if ((child as x.Element).getAttribute('lang') == locale) {
            title = child.firstChild.nodeValue;
          } else if (title == null && (child as x.Element).getAttribute('lang') == defaultLocale) {
            title = child.firstChild.nodeValue;
          }
        }
      } else if (child.nodeName == 'path' && child.firstChild != null && child.firstChild.nodeType == x.Node.TEXT_NODE) {
        path = child.firstChild.nodeValue;
      } else if (child.nodeName == 'type' && child.firstChild != null && child.firstChild.nodeType == x.Node.TEXT_NODE) {
        type = child.firstChild.nodeValue;
      } else if (child.nodeName == 'help') {
        if (child.firstChild != null && child.firstChild.nodeType == x.Node.TEXT_NODE) {
          if ((child as x.Element).getAttribute('lang') == locale) {
            help = child.firstChild.nodeValue;
          } else if (help == null && (child as x.Element).getAttribute('lang') == defaultLocale) {
            help = child.firstChild.nodeValue;
          }
        }
      }
    }
  }
  if (type == null) {
    print("Warning: missing type for template $title\n");
    type = 'problem';
  }
  x.Element refElement = doc.cfg.elementReference(type);
  MenuItem menuItem = new MenuItem(title, () => _insertTemplate(path), data: refElement);
  if (help != null)
    menuItem.toolTipText = help;
  return menuItem;
}

void _insertTemplate(String filePath) {
  try {
    x.DOMParser dp = new x.DOMParser();
    dp.parseFromURL(filePath).then((x.Document templateDoc) {
      x.Element root = templateDoc.documentElement;
      if (root == null)
        return;
      doc.removeWhitespace(root);
      DaxeNode dnRoot = NodeFactory.createFromNode(root, null);
      UndoableEdit edit;
      Position pos = page.getSelectionStart();
      if (dnRoot.nodeName == 'loncapa' && doc.getRootElement() != null)
        edit = doc.insertChildrenEdit(dnRoot, pos, checkValidity:true);
      else
        edit = new UndoableEdit.insertNode(pos, dnRoot);
      doc.doNewEdit(edit);
      page.updateAfterPathChange();
    });
  } on x.DOMException catch(ex) {
    h.window.alert(ex.toString());
  }
}

Index: modules/damieng/graphical_editor/loncapa_daxe/web/loncapa_daxe.html
+++ modules/damieng/graphical_editor/loncapa_daxe/web/loncapa_daxe.html
<!DOCTYPE html>
<!-- this file is used with the Dart Editor, for debugging -->
<html>
  <head>
    <meta charset="utf-8">
    <title>LON-CAPA Daxe</title>
    <link rel="stylesheet" href="packages/daxe/daxe.css">
    <link rel="stylesheet" href="loncapa_daxe.css">
    <script type="text/javascript"
      src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
    </script>
    <script src="LC_math_editor.min.js"></script>
</head>
  <body>
    <textarea id="tacursor"></textarea><span id="caret"></span>
    <div id="headers"></div>
    <div id="doc1"><div id="doc2"></div></div>
    <div id="path"></div>
    <script type="application/dart" src="loncapa_daxe.dart"></script>
    <script src="packages/browser/dart.js"></script>
  </body>
</html>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/parameters.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/parameters.xml
<?xml version="1.0" encoding="UTF-8"?>

<!--
  Implemented parameters, suggested based on the first matching context.
  We might want to create this file dynamically (not sure if this would be useful, but the list should be unique,
  although not all parameters make sense in the editor, so we might need to specify which ones do).
  TODO: Check which parameters should be in problem and which should be in part, and adjust document conversion.
  NOTE: Not sure if the "part_0" parameters defined in packages.tab should be here...
-->

<parameters>
  <context ancestor="problem">
    <parameter>
      <name>opendate</name>
      <type>date_start</type>
      <title lang="en">Problem Opening Date</title>
    </parameter>
    <parameter>
      <name>duedate</name>
      <type>date_end</type>
      <title lang="en">Problem Due Date</title>
    </parameter>
    <parameter>
      <name>answerdate</name>
      <type>date_start</type>
      <title lang="en">Problem Answer Date</title>
    </parameter>
  </context>
  <context ancestor="part">
    <parameter>
      <name>weight</name>
      <type>float_pos</type>
      <default>1</default>
      <title lang="en">Weight</title>
    </parameter>
    <parameter>
      <name>maxtries</name>
      <type>int_pos</type>
      <default>99</default>
      <title lang="en">Maximum Number of Tries</title>
    </parameter>
    <parameter>
      <name>hinttries</name>
      <type>int_pos</type>
      <default>1</default>
      <title lang="en">Number of Tries before hints appear</title>
    </parameter>
    <parameter>
      <name>type</name>
      <type>string_questiontype</type>
      <title lang="en">Question Type</title>
    </parameter>
    <parameter>
      <name>randomizeontries</name>
      <type>int_pos</type>
      <title lang="en">Tries before new Variation (if Qn. Type=randomizetry)</title>
    </parameter>
    <parameter>
      <name>printstartdate</name>
      <type>date_start</type>
      <title lang="en">Print start date</title>
    </parameter>
    <parameter>
      <name>printenddate</name>
      <type>date_end</type>
      <title lang="en">Print end date</title>
    </parameter>
    <parameter>
      <name>acc</name>
      <type>string_ip</type>
      <title lang="en">Client IP/Name Access Control</title>
    </parameter>
    <parameter>
      <name>problemstatus</name>
      <type>string_problemstatus</type>
      <default>yes</default>
      <title lang="en">Show Problem Status</title>
    </parameter>
    <parameter>
      <name>display</name>
      <type>string</type>
      <title lang="en">Part Description</title>
    </parameter>
    <parameter>
      <name>ordered</name>
      <type>string_yesno</type>
      <default>no</default>
      <title lang="en">Show Parts One-at-a-Time</title>
    </parameter>
    <parameter>
      <name>retrypartial</name>
      <type>string_yesno</type>
      <default>no</default>
      <title lang="en">Partially correct is answerable</title>
    </parameter>
  </context>
  <context ancestor="numericalhint">
    <parameter>
      <name>tol</name>
      <type>tolerance</type>
      <default>5%</default>
      <title lang="en">Numerical Tolerance</title>
    </parameter>
    <parameter>
      <name>sig</name>
      <type>int_range,0-16</type>
      <title lang="en">Significant Figures</title>
    </parameter>
  </context>
  <context ancestor="numericalresponse">
    <parameter>
      <name>tol</name>
      <type>tolerance</type>
      <default>5%</default>
      <title lang="en">Numerical Tolerance</title>
    </parameter>
    <parameter>
      <name>sig</name>
      <type>int_range</type>
      <title lang="en">Significant Digits</title>
    </parameter>
    <parameter>
      <name>turnoffunit</name>
      <type>string_yesno</type>
      <default>no</default>
      <title lang="en">Show Units - Disable Entry</title>
    </parameter>
    <parameter>
      <name>numbubbles</name>
      <type>int_pos</type>
      <default>8</default>
      <title lang="en">Number of bubbles in bubblesheet exam mode</title>
    </parameter>
    <parameter>
      <name>allowalgebra</name>
      <type>string_yesno</type>
      <default>no</default>
      <title lang="en">Allow algebraic entry</title>
    </parameter>
    <parameter>
      <name>customunits</name>
      <type>string_any</type>
      <title lang="en">Custom-defined Units</title>
    </parameter>
  </context>
  <context ancestor="formularesponse">
    <parameter>
      <name>tol</name>
      <type>tolerance</type>
      <default>0.00000000001</default>
      <title lang="en">Numerical Tolerance</title>
    </parameter>
    <parameter>
      <name>turnoffeditor</name>
      <type>string_yesno</type>
      <default>no</default>
      <title lang="en">Hide DragMath-Editor</title>
    </parameter>
  </context>
  <context ancestor="essayresponse">
    <parameter>
      <name>handgrade</name>
      <type>string_yesno</type>
      <default>yes</default>
      <title lang="en">Hand Graded</title>
    </parameter>
    <parameter>
      <name>maxcollaborators</name>
      <type>int_zeropos</type>
      <default>0</default>
      <title lang="en">Maximum Number of Collaborators</title>
    </parameter>
    <parameter>
      <name>uploadedfiletypes</name>
      <type>string_fileext</type>
      <title lang="en">Allowed File Extensions for Uploaded Files</title>
    </parameter>
    <parameter>
      <name>maxfilesize</name>
      <type>float_pos</type>
      <default>10.0</default>
      <title lang="en">Max. cumulative size (MB) for submitted files</title>
    </parameter>
  </context>
  <context ancestor="externalresponse">
    <parameter>
      <name>uploadedfiletypes</name>
      <type>string_fileext</type>
      <title lang="en">Allowed File Extensions for Uploaded Files</title>
    </parameter>
    <parameter>
      <name>maxfilesize</name>
      <type>float_pos</type>
      <default>10.0</default>
      <title lang="en">Max. cumulative size (MB) for submitted files</title>
    </parameter>
    <parameter>
      <name>maxcollaborators</name>
      <type>int_zeropos</type>
      <default>0</default>
      <title lang="en">Maximum Number of Collaborators</title>
    </parameter>
  </context>
  <context ancestor="mathresponse">
    <parameter>
      <name>turnoffeditor</name>
      <type>string_yesno</type>
      <default>no</default>
      <title lang="en">Hide DragMath-Editor</title>
    </parameter>
  </context>
  <context ancestor="optionresponse">
    <parameter>
      <name>lenient</name>
      <type>string_lenient</type>
      <default>default</default>
      <title lang="en">Lenient Grading (Partial Credit)</title>
    </parameter>
  </context>
  <context ancestor="matchresponse">
    <parameter>
      <name>lenient</name>
      <type>string_lenient</type>
      <default>default</default>
      <title lang="en">Lenient Grading (Partial Credit)</title>
    </parameter>
  </context>
</parameters>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates.xml
<?xml version="1.0" encoding="UTF-8"?>

<!--
NOTE: this file is supposed to be generated on-the-fly by the server.

It contains a list of menus (possibly containing submenus) to insert templates.

Structure:
  - templates contains several menu elements
  - menu contains several title, menu and item elements
  - item contains one path, one type and several title and help elements
  - title and help have a lang attribute
  - the type element has to be the name of an element in the schema
    (it is used to avoid having to parse the templates to check insertion possibilities)

One reason for using XML instead of JSON here is that we can use comments such as this one.
Another reason is that it is a lot easier to read.
-->

<templates>
  <menu>
    <title lang="en">Full Problems</title>
    <menu>
      <title lang="en">Multiple Choice</title>
      <item>
        <path>templates/problems/RadioResponse.problem.xml</path>
        <type>problem</type>
        <title lang="en">Radio Button Response</title>
      </item>
      <item>
        <path>templates/problems/randomvalueradio.problem.xml</path>
        <type>problem</type>
        <title lang="en">Randomized Question Stem Radio Button Resp.</title>
      </item>
      <item>
        <path>templates/problems/SimpleTrueFalse.problem.xml</path>
        <type>problem</type>
        <title lang="en">Option Response - True/False</title>
      </item>
      <item>
        <path>templates/problems/SelectFromOptions-4ConceptGoups.problem.xml</path>
        <type>problem</type>
        <title lang="en">Option Response - Concept Groups</title>
      </item>
      <item>
        <path>templates/problems/RandomLabelExample.problem.xml</path>
        <type>problem</type>
        <title lang="en">Randomly Labelled Image with Option Resp.</title>
      </item>
      <item>
        <path>templates/problems/SelectFromOptions-Simple.problem.xml</path>
        <type>problem</type>
        <title lang="en">Option Response - Matching</title>
      </item>
      <item>
        <path>templates/problems/SimpleMatching.problem.xml</path>
        <type>problem</type>
        <title lang="en">Matching Response</title>
      </item>
      <item>
        <path>templates/problems/SimpleRank.problem.xml</path>
        <type>problem</type>
        <title lang="en">Rank Response</title>
      </item>
    </menu>
    <menu>
      <title lang="en">Numerical</title>
      <item>
        <path>templates/problems/numerical.problem.xml</path>
        <type>problem</type>
        <title lang="en">Numerical Response</title>
      </item>
      <item>
        <path>templates/problems/MultipleAnswerEither.problem.xml</path>
        <type>problem</type>
        <title lang="en">One of Multiple Answers Numerical Problem</title>
      </item>
      <item>
        <path>templates/problems/numMultiAnswerUnordered.problem.xml</path>
        <type>problem</type>
        <title lang="en">Numerical Response Multiple Answers unordered</title>
      </item>
      <item>
        <path>templates/problems/numPrePro.problem.xml</path>
        <type>problem</type>
        <title lang="en">Numerical Response with Pre-Processing</title>
      </item>
      <item>
        <path>templates/problems/Plot_curve.problem.xml</path>
        <type>problem</type>
        <title lang="en">Curve Plot with Numerical Response</title>
      </item>
      <item>
        <path>templates/problems/Plot_data.problem.xml</path>
        <type>problem</type>
        <title lang="en">Data Plot with Numerical Response</title>
      </item>
      <item>
        <path>templates/problems/customunit.problem.xml</path>
        <type>problem</type>
        <title lang="en">Numerical Response with Custom Units</title>
      </item>
    </menu>
    <menu>
      <title lang="en">Algebra</title>
      <item>
        <path>templates/problems/SimpleMathResponse.problem.xml</path>
        <type>problem</type>
        <title lang="en">Math Response using C.A.S. MAXIMA</title>
      </item>
      <item>
        <path>templates/problems/SimpleMathResponseR.problem.xml</path>
        <type>problem</type>
        <title lang="en">Math Response using C.A.S. R</title>
      </item>
      <item>
        <path>templates/problems/HintMathResponse.problem.xml</path>
        <type>problem</type>
        <title lang="en">Math Response using C.A.S. and Hints</title>
      </item>
      <item>
        <path>templates/problems/SimpleFormula.problem.xml</path>
        <type>problem</type>
        <title lang="en">Formula Response with Samples</title>
      </item>
      <item>
        <path>templates/problems/HintFormula.problem.xml</path>
        <type>problem</type>
        <title lang="en">Formula Response using C.A.S. and Hints</title>
      </item>
      <item>
        <path>templates/problems/SimpleFormulaCAS.problem.xml</path>
        <type>problem</type>
        <title lang="en">Formula Response using C.A.S.</title>
      </item>
      <item>
        <path>templates/problems/MultipleAnswerUnordered.problem.xml</path>
        <type>problem</type>
        <title lang="en">Unordered Multi-Answer Formula Response</title>
      </item>
      <item>
        <path>templates/problems/Rnumerical.problem.xml</path>
        <type>problem</type>
        <title lang="en">Formula Response with C.A.S. R and Data Plot</title>
      </item>
      <item>
        <path>templates/problems/custom_equation.problem.xml</path>
        <type>problem</type>
        <title lang="en">Custom Response comparing Equations with C.A.S.</title>
      </item>
    </menu>
    <menu>
      <title lang="en">Chemistry</title>
      <item>
        <path>templates/problems/organic.problem.xml</path>
        <type>problem</type>
        <title lang="en">Organic Material Response</title>
      </item>
      <item>
        <path>templates/problems/organic_hint.problem.xml</path>
        <type>problem</type>
        <title lang="en">Organic Material Response with Hint</title>
      </item>
      <item>
        <path>templates/problems/reaction.problem.xml</path>
        <type>problem</type>
        <title lang="en">Chemical Reaction Response</title>
      </item>
      <item>
        <path>templates/problems/reaction_hint.problem.xml</path>
        <type>problem</type>
        <title lang="en">Chemical Reaction Response with Hints</title>
      </item>
    </menu>
    <menu>
      <title lang="en">Handgraded</title>
      <item>
        <path>templates/problems/DropBox.problem.xml</path>
        <type>problem</type>
        <title lang="en">Drop Box</title>
      </item>
      <item>
        <path>templates/problems/Essay.problem.xml</path>
        <type>problem</type>
        <title lang="en">Essay Response</title>
      </item>
    </menu>
    <menu>
      <title lang="en">Input-Dependent</title>
      <item>
        <path>templates/problems/answerdependent.problem.xml</path>
        <type>problem</type>
        <title lang="en">Using Learner Answer in Multipart Numerical</title>
      </item>
      <item>
        <path>templates/problems/dynamicgraph.problem.xml</path>
        <type>problem</type>
        <title lang="en">Using Learner Formula in Graph with Formula Resp.</title>
      </item>
      <item>
        <path>templates/problems/extreme.problem.xml</path>
        <type>problem</type>
        <title lang="en">Using Learner Formula in Graph with Math Resp.</title>
      </item>
    </menu>
    <menu>
      <title lang="en">Free Form</title>
      <item>
        <path>templates/problems/StringResponse.problem.xml</path>
        <type>problem</type>
        <title lang="en">String Response</title>
      </item>
      <item>
        <path>templates/problems/stringPrePro.problem.xml</path>
        <type>problem</type>
        <title lang="en">String Response with Pre-Processing</title>
      </item>
      <item>
        <path>templates/problems/CustomResponse.problem.xml</path>
        <type>problem</type>
        <title lang="en">Custom Response</title>
      </item>
      <item>
        <path>templates/problems/customhints.problem.xml</path>
        <type>problem</type>
        <title lang="en">Custom Response using C.A.S. and Hints</title>
      </item>
      <item>
        <path>templates/problems/custompartial.problem.xml</path>
        <type>problem</type>
        <title lang="en">Custom Response with Partial Credit</title>
      </item>
      <item>
        <path>templates/problems/functionplotback.problem.xml</path>
        <type>problem</type>
        <title lang="en">Functionplotresponse with Background Plot</title>
      </item>
      <item>
        <path>templates/problems/functionplotone.problem.xml</path>
        <type>problem</type>
        <title lang="en">Functionplotresponse with Labels</title>
      </item>
      <item>
        <path>templates/problems/functionplottwo.problem.xml</path>
        <type>problem</type>
        <title lang="en">Functionplotresponse with two Splines and Hints</title>
      </item>
      <item>
        <path>templates/problems/functionplotvector.problem.xml</path>
        <type>problem</type>
        <title lang="en">Functionplotresponse with Vectors and Hints</title>
      </item>
      <item>
        <path>templates/problems/sampleexternal.problem.xml</path>
        <type>problem</type>
        <title lang="en">External Response</title>
      </item>
    </menu>
    <menu>
      <title lang="en">Miscellaneous</title>
      <item>
        <path>templates/problems/blank.problem.xml</path>
        <type>problem</type>
        <title lang="en">Blank Problem</title>
      </item>
      <item>
        <path>templates/problems/ClickImageExample.problem.xml</path>
        <type>problem</type>
        <title lang="en">Click-On-Image Problem</title>
      </item>
      <item>
        <path>templates/problems/SelectFromOptions-multilingual.problem.xml</path>
        <type>problem</type>
        <title lang="en">Option Response - Matching (multilingual)</title>
      </item>
    </menu>
  </menu>
  <menu>
    <title lang="en">Responses</title>
    <item>
      <path>templates/responses/radiobuttonresponse.xml</path>
      <type>radiobuttonresponse</type>
      <title lang="en">One of N Statements</title>
    </item>
    <item>
      <path>templates/responses/optionresponse.xml</path>
      <type>optionresponse</type>
      <title lang="en">Select from Options</title>
    </item>
    <item>
      <path>templates/responses/stringresponse.xml</path>
      <type>stringresponse</type>
      <title lang="en">String</title>
    </item>
    <item>
      <path>templates/responses/essayresponse.xml</path>
      <type>essayresponse</type>
      <title lang="en">Essay</title>
    </item>
    <item>
      <path>templates/responses/rankresponse.xml</path>
      <type>rankresponse</type>
      <title lang="en">Rank Values</title>
    </item>
    <item>
      <path>templates/responses/matchresponse.xml</path>
      <type>matchresponse</type>
      <title lang="en">Match Two Lists</title>
    </item>
    <item>
      <path>templates/responses/numericalresponse.xml</path>
      <type>numericalresponse</type>
      <title lang="en">Numerical</title>
    </item>
    <item>
      <path>templates/responses/formularesponse.xml</path>
      <type>formularesponse</type>
      <title lang="en">Formula</title>
    </item>
    <item>
      <path>templates/responses/mathresponse.xml</path>
      <type>mathresponse</type>
      <title lang="en">Math</title>
    </item>
    <item>
      <path>templates/responses/organicresponse.xml</path>
      <type>organicresponse</type>
      <title lang="en">Organic Chemical Structure</title>
    </item>
    <item>
      <path>templates/responses/reactionresponse.xml</path>
      <type>reactionresponse</type>
      <title lang="en">Chemical Reaction</title>
    </item>
    <item>
      <path>templates/responses/functionplotresponse.xml</path>
      <type>functionplotresponse</type>
      <title lang="en">Function Plot</title>
    </item>
    <item>
      <path>templates/responses/customresponse.xml</path>
      <type>customresponse</type>
      <title lang="en">Custom</title>
    </item>
    <item>
      <path>templates/responses/externalresponse.xml</path>
      <type>externalresponse</type>
      <title lang="en">External</title>
    </item>
  </menu>
</templates>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/config/XHTML_config.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/config/XHTML_config.xml
<?xml version="1.0" encoding="UTF-8"?><CONFIG_JAXE>
  <LANGAGE>
    <FICHIER_SCHEMA nom="xhtml1-strict.xsd"/>
    <RACINE element="html"/>
  </LANGAGE>
  
  <MENUS>
    <MENU nom="structure">
      <MENU_INSERTION nom="html" type_noeud="element"/>
      <MENU_INSERTION nom="head" type_noeud="element"/>
      <MENU_INSERTION nom="title" type_noeud="element"/>
      <MENU_INSERTION nom="meta" type_noeud="element"/>
      <MENU_INSERTION nom="link" type_noeud="element"/>
      <MENU_INSERTION nom="body" type_noeud="element"/>
    </MENU>
    <MENU nom="divisions">
      <MENU_INSERTION nom="h1" type_noeud="element"/>
      <MENU_INSERTION nom="h2" type_noeud="element"/>
      <MENU_INSERTION nom="h3" type_noeud="element"/>
      <MENU_INSERTION nom="h4" type_noeud="element"/>
      <MENU_INSERTION nom="h5" type_noeud="element"/>
      <MENU_INSERTION nom="div" type_noeud="element"/>
      <MENU_INSERTION nom="hr" type_noeud="element"/>
    </MENU>
    <MENU nom="texte">
      <MENU_INSERTION nom="p" raccourci="P" type_noeud="element"/>
      <MENU_INSERTION nom="a" type_noeud="element"/>
      <MENU_INSERTION nom="blockquote" type_noeud="element"/>
      <MENU_INSERTION nom="pre" type_noeud="element"/>
      <MENU_INSERTION nom="em" type_noeud="element"/>
      <MENU_INSERTION nom="span" type_noeud="element"/>
      <MENU_INSERTION nom="strong" type_noeud="element"/>
      <MENU_INSERTION nom="tt" type_noeud="element"/>
      <MENU_INSERTION nom="abbr" type_noeud="element"/>
      <MENU_INSERTION nom="acronym" type_noeud="element"/>
      <MENU_INSERTION nom="address" type_noeud="element"/>
      <MENU_INSERTION nom="br" type_noeud="element"/>
      <MENU_INSERTION nom="cite" type_noeud="element"/>
      <MENU_INSERTION nom="code" type_noeud="element"/>
      <MENU_INSERTION nom="style" type_noeud="element"/>
      <MENU_INSERTION nom="commentaire" type_noeud="commentaire"/>
    </MENU>
    <MENU nom="listes_tables">
      <MENU_INSERTION nom="ul" type_noeud="element"/>
      <MENU_INSERTION nom="li" type_noeud="element"/>
      <MENU_INSERTION nom="ol" type_noeud="element"/>
      <MENU_INSERTION nom="dl" type_noeud="element"/>
      <MENU_INSERTION nom="dt" type_noeud="element"/>
      <MENU_INSERTION nom="dd" type_noeud="element"/>
      <MENU_INSERTION nom="table" type_noeud="element"/>
    </MENU>
    <MENU nom="objets">
      <MENU_INSERTION nom="img" type_noeud="element"/>
      <MENU_INSERTION nom="object" type_noeud="element"/>
      <MENU_INSERTION nom="param" type_noeud="element"/>
    </MENU>
    <MENU nom="formulaires">
      <MENU_INSERTION nom="form" type_noeud="element"/>
      <MENU_INSERTION nom="input" type_noeud="element"/>
      <MENU_INSERTION nom="label" type_noeud="element"/>
      <MENU_INSERTION nom="select" type_noeud="element"/>
      <MENU_INSERTION nom="option" type_noeud="element"/>
      <MENU_INSERTION nom="textarea" type_noeud="element"/>
    </MENU>
    <MENU nom="styles">
      <MENU_FONCTION classe="jaxe.FonctionNormal" nom="normal"/>
      <MENU_INSERTION nom="sup" type_noeud="element"/>
      <MENU_INSERTION nom="sub" type_noeud="element"/>
      <MENU_INSERTION nom="i" type_noeud="element"/>
      <MENU_INSERTION nom="b" type_noeud="element"/>
    </MENU>
  </MENUS>
  
  <AFFICHAGE_NOEUDS>
    <AFFICHAGE_ELEMENT element="html" type="division"/>
    <AFFICHAGE_ELEMENT element="head" type="division"/>
    <AFFICHAGE_ELEMENT element="title" type="string">
      <PARAMETRE nom="style" valeur="GRAS"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="meta" type="vide">
      <PARAMETRE nom="titreAtt" valeur="name"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="link" type="vide"/>
    <AFFICHAGE_ELEMENT element="body" type="division"/>
    <AFFICHAGE_ELEMENT element="h1" type="string">
      <PARAMETRE nom="style" valeur="GRAS"/>
      <PARAMETRE nom="police" valeur="Helvetica"/>
      <PARAMETRE nom="taille" valeur="24"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="h2" type="string">
      <PARAMETRE nom="style" valeur="GRAS"/>
      <PARAMETRE nom="taille" valeur="16"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="h3" type="string">
      <PARAMETRE nom="style" valeur="GRAS"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="h4" type="string"/>
    <AFFICHAGE_ELEMENT element="h5" type="string"/>
    <AFFICHAGE_ELEMENT element="div" type="division"/>
    <AFFICHAGE_ELEMENT element="hr" type="vide"/>
    <AFFICHAGE_ELEMENT element="p" type="hiddenp"/>
    <AFFICHAGE_ELEMENT element="a" type="string">
      <PARAMETRE nom="titreAtt" valeur="name"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="blockquote" type="zone"/>
    <AFFICHAGE_ELEMENT element="pre" type="zone"/>
    <AFFICHAGE_ELEMENT element="em" type="string">
      <PARAMETRE nom="style" valeur="ITALIQUE"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="span" type="stylespan"/>
    <AFFICHAGE_ELEMENT element="strong" type="string"/>
    <AFFICHAGE_ELEMENT element="tt" type="string">
      <PARAMETRE nom="police" valeur="Monospaced"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="abbr" type="string"/>
    <AFFICHAGE_ELEMENT element="acronym" type="string"/>
    <AFFICHAGE_ELEMENT element="address" type="string"/>
    <AFFICHAGE_ELEMENT element="br" type="vide"/>
    <AFFICHAGE_ELEMENT element="cite" type="string"/>
    <AFFICHAGE_ELEMENT element="code" type="zone"/>
    <AFFICHAGE_ELEMENT element="style" type="zone">
      <PARAMETRE nom="titreAtt" valeur="type"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="ul" type="wlist"/>
    <AFFICHAGE_ELEMENT element="li" type="witem"/>
    <AFFICHAGE_ELEMENT element="ol" type="wlist">
      <PARAMETRE nom="type" valeur="ol"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="dl" type="zone"/>
    <AFFICHAGE_ELEMENT element="dt" type="string"/>
    <AFFICHAGE_ELEMENT element="dd" type="string"/>
    <AFFICHAGE_ELEMENT element="table" type="tabletexte">
      <PARAMETRE nom="trTag" valeur="tr"/>
      <PARAMETRE nom="tdTag" valeur="td"/>
      <PARAMETRE nom="thTag" valeur="th"/>
      <PARAMETRE nom="colspanAttr" valeur="colspan"/>
      <PARAMETRE nom="rowspanAttr" valeur="rowspan"/>
      <PARAMETRE nom="alignAttr" valeur="align"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="tr" type="zone"/>
    <AFFICHAGE_ELEMENT element="td" type="string"/>
    <AFFICHAGE_ELEMENT element="th" type="string">
      <PARAMETRE nom="style" valeur="GRAS"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="img" type="fichier">
      <PARAMETRE nom="srcAtt" valeur="src"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="object" type="zone"/>
    <AFFICHAGE_ELEMENT element="param" type="vide">
      <PARAMETRE nom="titreAtt" valeur="name"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="form" type="zone"/>
    <AFFICHAGE_ELEMENT element="input" type="vide"/>
    <AFFICHAGE_ELEMENT element="label" type="string"/>
    <AFFICHAGE_ELEMENT element="select" type="zone"/>
    <AFFICHAGE_ELEMENT element="option" type="string"/>
    <AFFICHAGE_ELEMENT element="textarea" type="string"/>
    <AFFICHAGE_ELEMENT element="sup" type="style">
      <PARAMETRE nom="style" valeur="EXPOSANT"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="sub" type="style">
      <PARAMETRE nom="style" valeur="INDICE"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="i" type="style">
      <PARAMETRE nom="style" valeur="ITALIQUE"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="b" type="style">
      <PARAMETRE nom="style" valeur="GRAS"/>
    </AFFICHAGE_ELEMENT>
  </AFFICHAGE_NOEUDS>
  
  <EXPORTS>
    <EXPORT nom="HTML" sortie="HTML">
      <FICHIER_XSL nom="XHTML.xsl"/>
    </EXPORT>
  </EXPORTS>
  
  <STRINGS langue="en">
    <DESCRIPTION_CONFIG>XHTML strict</DESCRIPTION_CONFIG>
    
    <STRINGS_MENU menu="structure">
      <TITRE>Structure</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="html">
      <TITRE>html</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="head">
      <TITRE>head</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="title">
      <TITRE>title</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="meta">
      <TITRE>meta</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="link">
      <TITRE>link</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="body">
      <TITRE>body</TITRE>
    </STRINGS_MENU>
    
    <STRINGS_MENU menu="divisions">
      <TITRE>Divisions</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="h1">
      <TITRE>heading 1</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="h2">
      <TITRE>heading 2</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="h3">
      <TITRE>heading 3</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="h4">
      <TITRE>heading 4</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="h5">
      <TITRE>heading 5</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="div">
      <TITRE>division</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="hr">
      <TITRE>horizontal line</TITRE>
    </STRINGS_MENU>
    
    <STRINGS_MENU menu="texte">
      <TITRE>Text</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="p">
      <TITRE>paragraph</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="a">
      <TITRE>anchor</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="blockquote">
      <TITRE>blockquote</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="pre">
      <TITRE>preformat</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="em">
      <TITRE>emphasis</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="strong">
      <TITRE>strong</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="tt">
      <TITRE>fixed width (tt)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="abbr">
      <TITRE>abbreviation</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="acronym">
      <TITRE>acronym</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="address">
      <TITRE>address</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="br">
      <TITRE>line break (br)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="cite">
      <TITRE>citation</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="code">
      <TITRE>code</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="style">
      <TITRE>style</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="commentaire">
      <TITRE>XML comment</TITRE>
    </STRINGS_MENU>
    
    <STRINGS_MENU menu="listes_tables">
      <TITRE>Lists and Tables</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="ul">
      <TITRE>bulleted list (ul)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="li">
      <TITRE>list item (li)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="ol">
      <TITRE>ordered list (ol)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="dl">
      <TITRE>definition list (dl)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="dt">
      <TITRE>definition term (dt)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="dd">
      <TITRE>definition (dd)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="table">
      <TITRE>table</TITRE>
    </STRINGS_MENU>
    
    <STRINGS_MENU menu="objets">
      <TITRE>Objects</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="img">
      <TITRE>image</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="object">
      <TITRE>object</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="param">
      <TITRE>parameter</TITRE>
    </STRINGS_MENU>
    
    <STRINGS_MENU menu="formulaires">
      <TITRE>Forms</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="form">
      <TITRE>form</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="input">
      <TITRE>input</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="label">
      <TITRE>label</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="select">
      <TITRE>select</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="option">
      <TITRE>option</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="textarea">
      <TITRE>textarea</TITRE>
    </STRINGS_MENU>
    
    <STRINGS_MENU menu="styles">
      <TITRE>Styles</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="normal">
      <TITRE>standard</TITRE>
      <DOCUMENTATION>
        Removes all the styles in the selected text
      </DOCUMENTATION>
    </STRINGS_MENU>
    <STRINGS_MENU menu="sup">
      <TITRE>superscript</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="sub">
      <TITRE>subscript</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="i">
      <TITRE>italic</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="b">
      <TITRE>bold</TITRE>
    </STRINGS_MENU>
  </STRINGS>
  
  
  <STRINGS langue="fr">
    <DESCRIPTION_CONFIG>XHTML strict</DESCRIPTION_CONFIG>
    
    <STRINGS_MENU menu="structure">
      <TITRE>Structure</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="html">
      <TITRE>Document XHTML</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="head">
      <TITRE>Entête (head)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="title">
      <TITRE>Titre (title)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="meta">
      <TITRE>Métadonnée (meta)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="link">
      <TITRE>Lien (link)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="body">
      <TITRE>Corps du document (body)</TITRE>
    </STRINGS_MENU>
    
    <STRINGS_MENU menu="divisions">
      <TITRE>Divisions</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="h1">
      <TITRE>Titre niveau 1 (h1)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="h2">
      <TITRE>Titre niveau 2 (h2)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="h3">
      <TITRE>Titre niveau 3 (h3)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="h4">
      <TITRE>Titre niveau 4 (h4)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="h5">
      <TITRE>Titre niveau 5 (h5)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="div">
      <TITRE>Division (div)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="hr">
      <TITRE>ligne de division (hr)</TITRE>
    </STRINGS_MENU>
    
    <STRINGS_MENU menu="texte">
      <TITRE>Texte</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="p">
      <TITRE>Paragraphe (p)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="a">
      <TITRE>Ancre (a)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="blockquote">
      <TITRE>Indentation (blockquote)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="pre">
      <TITRE>Texte préformaté (pre)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="em">
      <TITRE>Emphase (em)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="strong">
      <TITRE>Appuyé (strong)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="tt">
      <TITRE>Télétype (tt)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="abbr">
      <TITRE>Abbréviation (abbr)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="acronym">
      <TITRE>Acronyme (acronym)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="address">
      <TITRE>Adresse (address)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="br">
      <TITRE>Saut de ligne (br)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="cite">
      <TITRE>Citation (cite)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="code">
      <TITRE>Code (code)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="style">
      <TITRE>Style (style)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="commentaire">
      <TITRE>Commentaire XML</TITRE>
    </STRINGS_MENU>
    
    <STRINGS_MENU menu="listes_tables">
      <TITRE>Listes/Tables</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="ul">
      <TITRE>Liste à points (ul)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="li">
      <TITRE>élément de liste (li)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="ol">
      <TITRE>Liste numérotée (ol)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="dl">
      <TITRE>Liste de définitions (dl)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="dt">
      <TITRE>Titre de liste de déf. (dt)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="dd">
      <TITRE>Définition de liste de déf. (dd)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="table">
      <TITRE>Table (table)</TITRE>
    </STRINGS_MENU>
    
    <STRINGS_MENU menu="objets">
      <TITRE>Objets</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="img">
      <TITRE>Image (img)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="object">
      <TITRE>Objet (object)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="param">
      <TITRE>Paramètre (param)</TITRE>
    </STRINGS_MENU>
    
    <STRINGS_MENU menu="formulaires">
      <TITRE>Formulaires</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="form">
      <TITRE>Formulaire (form)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="input">
      <TITRE>Entrée de donnée (input)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="label">
      <TITRE>Label (label)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="select">
      <TITRE>Menu de sélection (select)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="option">
      <TITRE>Option de sélection (option)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="textarea">
      <TITRE>Zone de texte (textarea)</TITRE>
    </STRINGS_MENU>
    
    <STRINGS_MENU menu="styles">
      <TITRE>Styles</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="normal">
      <TITRE>Sans style</TITRE>
      <DOCUMENTATION>
        Retire tous les styles du texte sélectionné, le transformant en texte normal
      </DOCUMENTATION>
    </STRINGS_MENU>
    <STRINGS_MENU menu="sup">
      <TITRE>Exposant (sup)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="sub">
      <TITRE>Indice (sub)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="i">
      <TITRE>Italique (i)</TITRE>
    </STRINGS_MENU>
    <STRINGS_MENU menu="b">
      <TITRE>Gras (b)</TITRE>
    </STRINGS_MENU>
  </STRINGS>
  
</CONFIG_JAXE>
Index: modules/damieng/graphical_editor/loncapa_daxe/web/config/loncapa.xsd
+++ modules/damieng/graphical_editor/loncapa_daxe/web/config/loncapa.xsd
<?xml version="1.0" encoding="UTF-8"?><xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xml:lang="en">
  <xs:annotation>
    <xs:documentation>
      XML schema for LON-CAPA 2 documents.
    </xs:documentation>
  </xs:annotation>
  
  <xs:import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="xml.xsd">
    <xs:annotation>
      <xs:documentation>
        This import is needed to use xml:space="preserve".
      </xs:documentation>
    </xs:annotation>
  </xs:import>
  
  <xs:annotation>
    <xs:documentation>
      Shared simple types.
    </xs:documentation>
  </xs:annotation>
  <xs:simpleType name="perl">
    <xs:restriction base="xs:string">
      <xs:pattern value="\s*-?(($|&)([#|$]*[A-Za-z][\w_]*|\{[A-Za-z][\w_]*\}))([\[\{].+[\]\}])*(\([^$&\)]+\))*\s*"/>
    </xs:restriction>
  </xs:simpleType>
  <xs:simpleType name="int-or-perl">
    <xs:union memberTypes="xs:int perl"/>
  </xs:simpleType>
  <xs:simpleType name="non-negative-int-or-perl">
    <xs:union memberTypes="xs:nonNegativeInteger perl"/>
  </xs:simpleType>
  <xs:simpleType name="decimal-or-perl">
    <xs:union memberTypes="xs:decimal perl"/>
  </xs:simpleType>
  <xs:simpleType name="real">
    <xs:restriction base="xs:string">
      <xs:pattern value="[+-]?\d*\.?\d*([eE][+-]?\d+)?"/>
    </xs:restriction>
  </xs:simpleType>
  <xs:simpleType name="real-or-perl">
    <xs:union memberTypes="real perl"/>
  </xs:simpleType>
  <xs:simpleType name="yesno">
    <xs:restriction base="xs:string">
      <xs:enumeration value="yes"/>
      <xs:enumeration value="no"/>
    </xs:restriction>
  </xs:simpleType>
  <xs:simpleType name="yesno-or-perl">
    <xs:union memberTypes="yesno perl"/>
  </xs:simpleType>
  <xs:simpleType name="onoff">
    <xs:restriction base="xs:string">
      <xs:enumeration value="on"/>
      <xs:enumeration value="off"/>
    </xs:restriction>
  </xs:simpleType>
  <xs:simpleType name="onoff-or-perl">
    <xs:union memberTypes="onoff perl"/>
  </xs:simpleType>
  <xs:simpleType name="color">
    <xs:restriction base="xs:string">
      <xs:pattern value="[x#][\da-fA-F]{6}"/>
    </xs:restriction>
  </xs:simpleType>
  <xs:simpleType name="color-or-perl">
    <xs:union memberTypes="color perl"/>
  </xs:simpleType>
  <xs:simpleType name="location">
    <xs:restriction base="xs:string">
      <xs:enumeration value="random"/>
      <xs:enumeration value="top"/>
      <xs:enumeration value="bottom"/>
    </xs:restriction>
  </xs:simpleType>
  <xs:simpleType name="location-or-perl">
    <xs:union memberTypes="location perl"/>
  </xs:simpleType>
  
  <xs:annotation>
    <xs:documentation>
      Shared attributes
    </xs:documentation>
  </xs:annotation>
  <xs:attribute default="normalsize" name="TeXsize">
    <xs:annotation>
      <xs:documentation>
        Size of LaTeX fonts used in printing.
        
        Possible values of TeXsize attribute:
        - tiny: smallest
        - scriptsize: very small
        - footnotesize: smaller
        - small: small
        - normalsize: normal
        - large: large
        - Large: larger
        - LARGE: even larger
        - huge: still larger
        - Huge: largest
        
        Note, that all parameters coincide with standard LaTeX commands for changing font size though you do not escape them.
      </xs:documentation>
    </xs:annotation>
    <xs:simpleType>
      <xs:union memberTypes="perl">
        <xs:simpleType>
          <xs:restriction base="xs:string">
            <xs:enumeration value="tiny"/>
            <xs:enumeration value="scriptsize"/>
            <xs:enumeration value="footnotesize"/>
            <xs:enumeration value="small"/>
            <xs:enumeration value="normalsize"/>
            <xs:enumeration value="large"/>
            <xs:enumeration value="Large"/>
            <xs:enumeration value="LARGE"/>
            <xs:enumeration value="huge"/>
            <xs:enumeration value="Huge"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:union>
    </xs:simpleType>
  </xs:attribute>
  
  <xs:annotation>
    <xs:documentation>
      Problem (root element)
    </xs:documentation>
  </xs:annotation>
  <xs:element name="problem">
    <xs:annotation>
      <xs:documentation>
        Root for .problem documents.
        
        This must be first in the file. It sets up the header of the webpage and generates the submit buttons. It also handles due dates properly.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-with-parts"/>
        <xs:group ref="inserts"/>
        <xs:element ref="allow"/>
        <xs:element ref="meta"/>
        <xs:element ref="parameter"/>
        <xs:element ref="parserlib"/>
        <xs:element ref="scriptlib"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  
  <xs:annotation>
    <xs:documentation>
      Library (root element)
    </xs:documentation>
  </xs:annotation>
  <xs:element name="library">
    <xs:annotation>
      <xs:documentation>
        Root for .library documents.
        A LON-CAPA .library file can contain just a script block, or just response items, or both.
        Library content is loaded into a problem statement by using an <import> tag.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-with-responses"/>
        <xs:group ref="inserts"/>
        <xs:element ref="part"/>
        <xs:element ref="parserlib"/>
        <xs:element ref="scriptlib"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  
  <xs:annotation>
    <xs:documentation>
      Groups of block and inline elements.
    </xs:documentation>
  </xs:annotation>
  <xs:group name="text-with-parts">
    <xs:annotation>
      <xs:documentation>
        List of block and inline elements mixed with text. These elements can contain parts and responses.
      </xs:documentation>
    </xs:annotation>
    <xs:choice>
      <xs:element ref="part"/>
      <xs:group ref="responses"/>
      <xs:group ref="blocks-with-parts"/>
      <xs:group ref="inlines"/>
    </xs:choice>
  </xs:group>
  <xs:group name="text-with-responses">
    <xs:annotation>
      <xs:documentation>
        List of block and inline elements mixed with text. These elements can contain responses but not parts.
      </xs:documentation>
    </xs:annotation>
    <xs:choice>
      <xs:group ref="responses"/>
      <xs:group ref="blocks-with-responses"/>
      <xs:group ref="inlines"/>
    </xs:choice>
  </xs:group>
  <xs:group name="text-only">
    <xs:annotation>
      <xs:documentation>
        List of block and inline elements mixed with text. These elements cannot contain responses or parts.
      </xs:documentation>
    </xs:annotation>
    <xs:choice>
      <xs:group ref="blocks-with-text"/>
      <xs:group ref="inlines"/>
    </xs:choice>
  </xs:group>
  <xs:group name="universal-blocks">
    <xs:annotation>
      <xs:documentation>
        Blocks with a content that does not depend on the scope, and that can appear anywhere with text and blocks.
      </xs:documentation>
    </xs:annotation>
    <xs:choice>
      <xs:element ref="randomlabel"/>
      <xs:element ref="import"/>
      <xs:element ref="while"/>
      <xs:element ref="tex"/>
      <xs:element ref="print"/>
      <xs:element ref="web"/>
      <xs:element ref="standalone"/>
      <xs:element ref="script"/>
      <xs:element ref="languageblock"/>
      <xs:element ref="translated"/>
      <xs:element ref="window"/>
      <xs:element ref="windowlink"/>
      <xs:element ref="togglebox"/>
      <xs:element ref="instructorcomment"/>
      <xs:element ref="comment"/>
      <xs:element ref="gnuplot"/>
      <xs:element ref="organicstructure"/>
      <xs:element ref="drawimage"/>
      <xs:element ref="solved"/>
      <xs:element ref="notsolved"/>
      
      <xs:group ref="heading"/>
      <xs:element ref="noscript"/>
      <xs:element ref="header"/>
      <xs:element ref="footer"/>
      <xs:element ref="aside"/>
      <xs:element ref="pre"/>
      <xs:element ref="hr"/>
      <xs:element ref="address"/>
      <xs:element ref="blockquote"/>
      <xs:element ref="figure"/>
      <xs:element ref="object"/>
      <xs:element ref="applet"/>
      <xs:element ref="embed"/>
      <xs:element ref="video"/>
      <xs:element ref="audio"/>
      <xs:element ref="map"/>
      <xs:element ref="canvas"/>
      <xs:element ref="form"/>
      <xs:element ref="fieldset"/>
      <xs:element ref="iframe"/>
    </xs:choice>
  </xs:group>
  <xs:group name="blocks-with-parts">
    <xs:choice>
      <xs:group ref="universal-blocks"/>
      
      <xs:element name="block" type="block-with-parts"/>
      <xs:element name="problemtype" type="problemtype-with-parts"/>
      <xs:element name="randomlist" type="randomlist-with-parts"/>
      
      <xs:element name="section" type="section-with-parts"/>
      <xs:element name="div" type="div-with-parts"/>
      <xs:element name="p" type="p-with-responses"/>
      <xs:element name="ul" type="ul-with-parts"/>
      <xs:element name="ol" type="ol-with-parts"/>
      <xs:element name="dl" type="dl-with-parts"/>
      <xs:element name="table" type="table-with-parts"/>
    </xs:choice>
  </xs:group>
  <xs:group name="blocks-with-responses">
    <xs:choice>
      <xs:group ref="universal-blocks"/>
      
      <xs:element name="block" type="block-with-responses"/>
      <xs:element name="problemtype" type="problemtype-with-responses"/>
      <xs:element name="randomlist" type="randomlist-with-responses"/>
      
      <xs:element name="section" type="section-with-responses"/>
      <xs:element name="div" type="div-with-responses"/>
      <xs:element name="p" type="p-with-responses"/>
      <xs:element name="ul" type="ul-with-responses"/>
      <xs:element name="ol" type="ol-with-responses"/>
      <xs:element name="dl" type="dl-with-responses"/>
      <xs:element name="table" type="table-with-responses"/>
    </xs:choice>
  </xs:group>
  <xs:group name="blocks-with-text">
    <xs:choice>
      <xs:group ref="universal-blocks"/>
      
      <xs:element name="block" type="block-with-text"/>
      <xs:element name="problemtype" type="problemtype-with-text"/>
      <xs:element name="randomlist" type="randomlist-with-text"/>
      
      <xs:element name="section" type="section-with-text"/>
      <xs:element name="div" type="div-with-text"/>
      <xs:element name="p" type="p-with-text"/>
      <xs:element name="ul" type="ul-with-text"/>
      <xs:element name="ol" type="ol-with-text"/>
      <xs:element name="dl" type="dl-with-text"/>
      <xs:element name="table" type="table-with-text"/>
    </xs:choice>
  </xs:group>
  <xs:group name="inlines">
    <xs:choice>
      <xs:element ref="display"/>
      <xs:element ref="m"/>
      <xs:element ref="lm"/>
      <xs:element ref="chem"/>
      <xs:element ref="num"/>
      <xs:element ref="parse"/>
      <xs:element ref="algebra"/>
      <xs:element ref="textline"/>
      <xs:element ref="displayweight"/>
      <xs:element ref="displaystudentphoto"/>
      
      <xs:element ref="span"/>
      <xs:element ref="a"/>
      <xs:element ref="strong"/>
      <xs:element ref="em"/>
      <xs:element ref="b"/>
      <xs:element ref="i"/>
      <xs:element ref="sup"/>
      <xs:element ref="sub"/>
      <xs:element ref="code"/>
      <xs:element ref="kbd"/>
      <xs:element ref="samp"/>
      <xs:element ref="cite"/>
      <xs:element ref="q"/>
      <xs:element ref="tt"/>
      <xs:element ref="ins"/>
      <xs:element ref="del"/>
      <xs:element ref="var"/>
      <xs:element ref="small"/>
      <xs:element ref="big"/>
      <xs:element ref="br"/>
      <xs:element ref="img"/>
      <xs:element ref="input"/>
      <xs:element ref="select"/>
      <xs:element ref="textarea"/>
      <xs:element ref="label"/>
      <xs:element ref="button"/>
    </xs:choice>
  </xs:group>
  <xs:group name="inserts">
    <xs:annotation>
      <xs:documentation>
        List of elements that insert something if a condition is verified.
      </xs:documentation>
    </xs:annotation>
    <xs:choice>
      <xs:element ref="displaytitle"/>
      <xs:element ref="displayduedate"/>
      <xs:element ref="preduedate"/>
      <xs:element ref="postanswerdate"/>
    </xs:choice>
  </xs:group>
  
  <xs:annotation>
    <xs:documentation>
      List of responses
    </xs:documentation>
  </xs:annotation>
  <xs:group name="responses">
    <xs:choice>
      <xs:group ref="inlineResponses"/>
      <xs:group ref="blockResponses"/>
    </xs:choice>
  </xs:group>
  <xs:group name="inlineResponses">
    <xs:choice>
      <xs:element ref="stringresponse"/>
      <xs:element ref="optionresponse"/>
      <xs:element ref="numericalresponse"/>
      <xs:element ref="formularesponse"/>
      <xs:element ref="mathresponse"/>
      <xs:element ref="organicresponse"/>
      <xs:element ref="reactionresponse"/>
      <xs:element ref="customresponse"/>
      <xs:element ref="externalresponse"/>
    </xs:choice>
  </xs:group>
  <xs:group name="blockResponses">
    <xs:choice>
      <xs:element ref="essayresponse"/>
      <xs:element ref="radiobuttonresponse"/>
      <xs:element ref="matchresponse"/>
      <xs:element ref="rankresponse"/>
      <xs:element ref="imageresponse"/>
      <xs:element ref="functionplotresponse"/>
      <xs:element ref="dataresponse"/>
    </xs:choice>
  </xs:group>
  <xs:attributeGroup name="response-identification">
    <xs:attribute name="id" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          Unique identifier for the response in the document. If this isn’t set, it will be set during the publication step. It is used to assign parameter names in a way that can be tracked if an instructor modifies by hand.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="name" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          If set, the name will be used by the resource assembly tool when one is modifying parameters.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:attributeGroup>
  
  <xs:annotation>
    <xs:documentation>
      String response
    </xs:documentation>
  </xs:annotation>
  <xs:element name="stringresponse">
    <xs:annotation>
      <xs:documentation>
        Query for a string.
        An internal textline tag is necessary for the student’s response to go in. It can check the string for either case or order.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
        <xs:element ref="hiddenline"/>
        <xs:element ref="hintgroup"/>
        <xs:element ref="stringhint"/>
        <xs:element ref="hintpart"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="response-identification"/>
      <xs:attribute name="answer" type="xs:string" use="required">
        <xs:annotation>
          <xs:documentation>
            the correct answer, either a perl list or scalar
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="cs" name="type">
        <xs:annotation>
          <xs:documentation>
            Specifies how the string is checked (like the CAPA styles). Possible values are:
            – cs: case sensitive, order important.
            – ci: case insensitive, order important.
            – mc: case insensitive, order unimportant.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="cs"/>
                <xs:enumeration value="ci"/>
                <xs:enumeration value="mc"/>
                <xs:enumeration value="re"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="answerdisplay" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            String to display for answer
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="preprocess" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Pre-Processor Subroutine
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  
  <xs:annotation>
    <xs:documentation>
      Essay response
    </xs:documentation>
  </xs:annotation>
  <xs:element name="essayresponse">
    <xs:annotation>
      <xs:documentation>
        Query for a long text or a line, possibly with spell checking.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
        <xs:element ref="textfield"/>
        <xs:element ref="hiddensubmission"/>
        <xs:element ref="hiddenline"/>
        <xs:element ref="hintgroup"/>
        <xs:element ref="stringhint"/>
        <xs:element ref="hintpart"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="response-identification"/>
    </xs:complexType>
  </xs:element>
  
  <xs:annotation>
    <xs:documentation>
      Radio button response
    </xs:documentation>
  </xs:annotation>
  <xs:element name="radiobuttonresponse">
    <xs:annotation>
      <xs:documentation>
        Query for a single choice among several statements.
        The value of the foils can only be true, false, or unused.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
        <xs:element name="foilgroup" type="radiobuttonresponse--foilgroup"/>
        <xs:element ref="hintgroup"/>
        <xs:element ref="radiobuttonhint"/>
        <xs:element ref="hintpart"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="response-identification"/>
      <xs:attribute name="max" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            Max Number Of Shown Foils
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="yes" name="randomize" type="yesno-or-perl">
        <xs:annotation>
          <xs:documentation>
            Randomize Foil Order
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="vertical" name="direction">
        <xs:annotation>
          <xs:documentation>
            Display Direction
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="vertical"/>
                <xs:enumeration value="horizontal"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:complexType name="radiobuttonresponse--foilgroup">
    <xs:annotation>
      <xs:documentation>Collection of Foils</xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded">
      <xs:element name="conceptgroup" type="radiobuttonresponse--conceptgroup"/>
      <xs:element name="foil" type="radiobuttonresponse--foil"/>
      <xs:element ref="comment"/>
    </xs:choice>
  </xs:complexType>
  <xs:complexType name="radiobuttonresponse--conceptgroup">
    <xs:annotation>
      <xs:documentation>
        Collection of similar foils.
        When a problem is displayed, only one of the contained foils is selected for display.
      </xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded">
      <xs:element name="foil" type="radiobuttonresponse--foil"/>
    </xs:choice>
    <xs:attribute name="concept" type="xs:string" use="required"/>
  </xs:complexType>
  <xs:complexType mixed="true" name="radiobuttonresponse--foil">
    <xs:annotation>
      <xs:documentation>
        Statement next to the radio button.
      </xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded" minOccurs="0">
      <xs:group ref="text-only"/>
      <xs:element ref="parserlib"/>
      <xs:element ref="scriptlib"/>
    </xs:choice>
    <xs:attribute name="name" type="xs:string" use="required"/>
    <xs:attribute name="value" use="required">
      <xs:annotation>
        <xs:documentation>
          Correct Option (true, false, or unused).
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="true"/>
              <xs:enumeration value="false"/>
              <xs:enumeration value="unused"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute default="random" name="location" type="location-or-perl"/>
  </xs:complexType>
  
  <xs:annotation>
    <xs:documentation>
      Option response
    </xs:documentation>
  </xs:annotation>
  <xs:element name="optionresponse">
    <xs:annotation>
      <xs:documentation>
        Query for a choice for each given statement.
        
        Option Response problems present foils to the student with drop-down boxes. The student can select the matching choice for the foils from a list of choices. Optionally, the foils may be bundled into Concept Groups and the system will select one foil from each group to display to the student.
        
        By default, the list of options is presented in front of the foils. Using the optional drawoptionlist element, the list of options can be embedded into the foil. 
        
        The foilgroup is required to have an options attribute which should be a perl list of possible options for the student.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
        <xs:element name="foilgroup" type="optionresponse--foilgroup"/>
        <xs:element ref="hintgroup"/>
        <xs:element ref="optionhint"/>
        <xs:element ref="hintpart"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="response-identification"/>
      <xs:attribute name="max" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            Max Number Of Shown Foils
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="yes" name="randomize" type="yesno-or-perl">
        <xs:annotation>
          <xs:documentation>
            Randomize Foil Order
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="horizontal" name="TeXlayout">
        <xs:annotation>
          <xs:documentation>
            Display of options when printed
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="horizontal"/>
                <xs:enumeration value="vertical"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:complexType name="optionresponse--foilgroup">
    <xs:annotation>
      <xs:documentation>Collection of Foils</xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded">
      <xs:element name="conceptgroup" type="optionresponse--conceptgroup"/>
      <xs:element name="foil" type="optionresponse--foil"/>
      <xs:element ref="comment"/>
    </xs:choice>
    <xs:attribute name="options" use="required">
      <xs:annotation>
        <xs:documentation>
          Perl list of possible foil values.
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:pattern value="@[a-zA-Z0-9_\-]+"/>
            </xs:restriction>
          </xs:simpleType>
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:pattern value="\(.+\)"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute name="texoptions">
      <xs:annotation>
        <xs:documentation>
          Print options
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="nochoice"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute name="checkboxvalue" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          Two-option checkboxes for
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="checkboxoptions">
      <xs:annotation>
        <xs:documentation>
          Checkbox options
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="nochoice"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType name="optionresponse--conceptgroup">
    <xs:annotation>
      <xs:documentation>
        Collection of similar foils.
        When a problem is displayed, only one of the contained foils is selected for display.
      </xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded">
      <xs:element name="foil" type="optionresponse--foil"/>
    </xs:choice>
    <xs:attribute name="concept" type="xs:string" use="required"/>
  </xs:complexType>
  <xs:complexType mixed="true" name="optionresponse--foil">
    <xs:annotation>
      <xs:documentation>
        Statement next to the drop-down box.
      </xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded" minOccurs="0">
      <xs:group ref="text-only"/>
      <xs:element ref="parserlib"/>
      <xs:element ref="scriptlib"/>
      <xs:element name="drawoptionlist" type="optionresponse--drawoptionlist"/>
    </xs:choice>
    <xs:attribute name="name" type="xs:string" use="required"/>
    <xs:attribute name="value" type="xs:string" use="required">
      <xs:annotation>
        <xs:documentation>
          Correct Option
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="random" name="location" type="location-or-perl"/>
  </xs:complexType>
  <xs:complexType name="optionresponse--drawoptionlist">
    <xs:annotation>
      <xs:documentation>Draw Option List</xs:documentation>
    </xs:annotation>
  </xs:complexType>
  
  <xs:annotation>
    <xs:documentation>
      Match response
    </xs:documentation>
  </xs:annotation>
  <xs:element name="matchresponse">
    <xs:annotation>
      <xs:documentation>
        Query for matches betweens items from two lists.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
        <xs:element name="foilgroup" type="matchresponse--foilgroup"/>
        <xs:element ref="hintgroup"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="response-identification"/>
      <xs:attribute name="max" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            Max Number Of Shown Foils
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="yes" name="randomize" type="yesno-or-perl">
        <xs:annotation>
          <xs:documentation>
            Randomize Foil Order
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:complexType name="matchresponse--itemgroup">
    <xs:annotation>
      <xs:documentation>Items to Match</xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded">
      <xs:element name="item" type="matchresponse--item"/>
    </xs:choice>
    <xs:attribute default="yes" name="randomize" type="yesno-or-perl">
      <xs:annotation>
        <xs:documentation>
          Randomize Order
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="top" name="location">
      <xs:annotation>
        <xs:documentation>
          Items Display Location
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="top"/>
              <xs:enumeration value="bottom"/>
              <xs:enumeration value="left"/>
              <xs:enumeration value="right"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute default="vertical" name="direction">
      <xs:annotation>
        <xs:documentation>
          Items Display Direction
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="vertical"/>
              <xs:enumeration value="horizontal"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute name="columns">
      <xs:annotation>
        <xs:documentation>
          Items Columns
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:int">
              <xs:enumeration value="1"/>
              <xs:enumeration value="2"/>
              <xs:enumeration value="3"/>
              <xs:enumeration value="4"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute name="TeXitemgroupwidth">
      <xs:annotation>
        <xs:documentation>
          TeXitemgroupwidth attribute allows you to specify the width of table with items for matching. The value of this attribute defines the width in percents with respect to text line width.
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:restriction base="xs:string">
          <xs:pattern value="\d+%"/>
        </xs:restriction>
      </xs:simpleType>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType mixed="true" name="matchresponse--item">
    <xs:annotation>
      <xs:documentation>Item</xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded" minOccurs="0">
      <xs:group ref="text-only"/>
      <xs:element ref="parserlib"/>
      <xs:element ref="scriptlib"/>
    </xs:choice>
    <xs:attribute name="name" type="xs:string"/>
    <xs:attribute default="random" name="location" type="location-or-perl"/>
  </xs:complexType>
  <xs:complexType name="matchresponse--foilgroup">
    <xs:annotation>
      <xs:documentation>Collection of Foils</xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded">
      <xs:element name="itemgroup" type="matchresponse--itemgroup"/>
      <xs:element name="conceptgroup" type="matchresponse--conceptgroup"/>
      <xs:element name="foil" type="matchresponse--foil"/>
      <xs:element ref="comment"/>
    </xs:choice>
  </xs:complexType>
  <xs:complexType name="matchresponse--conceptgroup">
    <xs:annotation>
      <xs:documentation>
        Collection of similar foils.
        When a problem is displayed, only one of the contained foils is selected for display.
      </xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded">
      <xs:element name="foil" type="matchresponse--foil"/>
    </xs:choice>
    <xs:attribute name="concept" type="xs:string" use="required"/>
  </xs:complexType>
  <xs:complexType mixed="true" name="matchresponse--foil">
    <xs:annotation>
      <xs:documentation>Foil</xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded" minOccurs="0">
      <xs:group ref="text-only"/>
      <xs:element ref="parserlib"/>
      <xs:element ref="scriptlib"/>
    </xs:choice>
    <xs:attribute name="name" type="xs:string" use="required"/>
    <xs:attribute name="value" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          Correct Option
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="random" name="location" type="location-or-perl"/>
  </xs:complexType>
  
  <xs:annotation>
    <xs:documentation>
      Rank response
    </xs:documentation>
  </xs:annotation>
  <xs:element name="rankresponse">
    <xs:annotation>
      <xs:documentation>
        Query to sort a list of items in the right order.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
        <xs:element name="foilgroup" type="rankresponse--foilgroup"/>
        <xs:element ref="hintgroup"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="response-identification"/>
      <xs:attribute name="max" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            Max Number Of Shown Foils
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="yes" name="randomize" type="yesno-or-perl">
        <xs:annotation>
          <xs:documentation>
            Randomize Foil Order
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:complexType name="rankresponse--foilgroup">
    <xs:annotation>
      <xs:documentation>Collection of Foils</xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded">
      <xs:element name="conceptgroup" type="rankresponse--conceptgroup"/>
      <xs:element name="foil" type="rankresponse--foil"/>
      <xs:element ref="comment"/>
    </xs:choice>
  </xs:complexType>
  <xs:complexType name="rankresponse--conceptgroup">
    <xs:annotation>
      <xs:documentation>
        Collection of similar foils.
        When a problem is displayed, only one of the contained foils is selected for display.
      </xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded">
      <xs:element name="foil" type="rankresponse--foil"/>
    </xs:choice>
    <xs:attribute name="concept" type="xs:string" use="required"/>
  </xs:complexType>
  <xs:complexType mixed="true" name="rankresponse--foil">
    <xs:annotation>
      <xs:documentation>Foil</xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded" minOccurs="0">
      <xs:group ref="text-only"/>
      <xs:element ref="parserlib"/>
      <xs:element ref="scriptlib"/>
    </xs:choice>
    <xs:attribute name="name" type="xs:string" use="required"/>
    <xs:attribute name="value" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          Rank Value
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="random" name="location" type="location-or-perl"/>
  </xs:complexType>
  
  <xs:annotation>
    <xs:documentation>
      Image response
    </xs:documentation>
  </xs:annotation>
  <xs:element name="imageresponse">
    <xs:annotation>
      <xs:documentation>
        Query for the selection of an image in a list.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
        <xs:element name="foilgroup" type="imageresponse--foilgroup"/>
        <xs:element ref="hintgroup"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="response-identification"/>
      <xs:attribute name="max" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            Max Number Of Shown Foils
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:complexType name="imageresponse--foilgroup">
    <xs:annotation>
      <xs:documentation>Collection of Imageresponse foils</xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded">
      <xs:element name="conceptgroup" type="imageresponse--conceptgroup"/>
      <xs:element name="foil" type="imageresponse--foil"/>
      <xs:element ref="comment"/>
    </xs:choice>
  </xs:complexType>
  <xs:complexType name="imageresponse--conceptgroup">
    <xs:annotation>
      <xs:documentation>
        Collection of similar foils.
        When a problem is displayed, only one of the contained foils is selected for display.
      </xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded">
      <xs:element name="foil" type="imageresponse--foil"/>
    </xs:choice>
    <xs:attribute name="concept" type="xs:string" use="required"/>
  </xs:complexType>
  <xs:complexType name="imageresponse--foil">
    <xs:annotation>
      <xs:documentation>Image response foil. image and rectangle are required.</xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded" minOccurs="0">
      <xs:element name="image" type="imageresponse--image"/>
      <xs:element name="polygon" type="imageresponse--polygon"/>
      <xs:element name="rectangle" type="imageresponse--rectangle"/>
      <xs:element name="text" type="imageresponse--text"/>
    </xs:choice>
    <xs:attribute name="name" type="xs:string" use="required"/>
  </xs:complexType>
  <xs:complexType mixed="true" name="imageresponse--image">
    <xs:annotation>
      <xs:documentation>
        Imageresponse Image (contains the image source file).
        
        The delimited text should correspond to a published image resource.
        Example: <image>/res/adm/includes/templates/man1.jpg</image>. The following image formats are recommended - gif, jpg or png. Other formats may work, but there may be printing or display issues. The image should only appear once per foil.
      </xs:documentation>
    </xs:annotation>
    <xs:choice minOccurs="0">
      <xs:element ref="gnuplot"/>
    </xs:choice>
  </xs:complexType>
  <xs:simpleType name="imageresponse--rectangle">
    <xs:annotation>
      <xs:documentation>
        Rectangular area in image (contains coordinate pairs).
        
        The delimited text specifies a rectangular area that is correct, specified as (x1,y1)-(x2,y2), where x1, x2, y1, and y2 are number corresponding to the x and y coordinates of two corners that define a rectangle which specifies where the right answer for this foil is located on the image. For example, (0,0)-(100,200) will specify that a rectangle 100 pixels wide and 200 pixels tall, situated in the upper left of the image, is correct. At least one rectangle is required; multiple rectangles may be specified.
      </xs:documentation>
    </xs:annotation>
    <xs:union memberTypes="perl">
      <xs:simpleType>
        <xs:restriction base="xs:string">
          <xs:pattern value="\s*\(.+\)-\(.+\)\s*"/>
        </xs:restriction>
      </xs:simpleType>
    </xs:union>
  </xs:simpleType>
  <xs:simpleType name="imageresponse--polygon">
    <xs:annotation>
      <xs:documentation>Polygonal area in image (contains coordinate list)</xs:documentation>
    </xs:annotation>
    <xs:union memberTypes="perl">
      <xs:simpleType>
        <xs:restriction base="xs:string">
          <xs:pattern value="\s*\(.+\)(-\(.+\))+\s*"/>
        </xs:restriction>
      </xs:simpleType>
    </xs:union>
  </xs:simpleType>
  <xs:complexType mixed="true" name="imageresponse--text">
    <xs:annotation>
      <xs:documentation>
        Text to describe option
        
        The delimited text is printed before the image is shown on the screen.
        This text is typically used to describe to the student what they are expected to click on.
      </xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded" minOccurs="0">
      <xs:group ref="text-only"/>
      <xs:element ref="parserlib"/>
      <xs:element ref="scriptlib"/>
    </xs:choice>
  </xs:complexType>
  
  <xs:annotation>
    <xs:documentation>
      Numerical response
    </xs:documentation>
  </xs:annotation>
  <xs:element name="numericalresponse">
    <xs:annotation>
      <xs:documentation>
        Query for one or several numbers, possibly with units.
        
        A tolerance parameter should be used to determine how closely the system will require the student’s answer to be in order to count it correct. The tolerance will default to zero if it is not defined. The tolerance parameter should always be defined for a numerical problem unless you are certain only integer answers are generated from your script and you want students to reply with exactly that integer.
        
        A significant figures parameter tells the system how many significant figures there are in the problem, as either a single number, e.g. 3, or a range of acceptable values, expressed as min,max. The system will check to make sure that the student’s answer contains this many significant digits, useful in many scientific calculations.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
        <xs:element name="answergroup" type="caparesponse--answergroup"/>
        <xs:element ref="hiddenline"/>
        <xs:element ref="hintgroup"/>
        <xs:element ref="numericalhint"/>
        <xs:element ref="hintpart"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="response-identification"/>
      <xs:attribute name="answer" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            The answer the system is looking for. The answer can use variables calculated/defined in the problem’s script block, allowing the answer to be determined dynamically (including randomization).
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="incorrect" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Incorrect Answers
            When switched into exam ("bubble sheet") mode, LON-CAPA usually create wrong answers automatically. To specify wrong answers yourself, you need to provide an array of incorrect values in this attribute. You need to provide at least as many incorrects as 1 less than the number of bubbles on the exam. You can provide more if you want to.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="unit" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Expected units for the answer. For instance, "m/s^2" or "km/(A*hr)".
            LON-CAPA will automatically perform some conversions between units of the same dimension when units are provided for a problem. You can provide an answer of "1.45 km" for a distance. If the computer expects the answer in cm, it will convert your answer before comparing against the numerical solution.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="format" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            You can format the number displayed by the computer as the answer. For instance, if the answer is one-third, the computer will display that it computed ".333333333" as the answer. If you'd like to shorten that, you can use the Format field. Format strings like "2E" (without the quotes) will display three significant digits in scientific notation. Format strings like "2f" will display two digits after the decimal point. Format strings like "2s" will round a number to 2 significant digits.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="preprocess" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Pre-Processor Subroutine
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:complexType name="caparesponse--answergroup">
    <xs:annotation>
      <xs:documentation>Collection of Answers</xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded">
      <xs:element name="answer" type="caparesponse--answer"/>
    </xs:choice>
    <xs:attribute default="ordered" name="type">
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="ordered"/>
              <xs:enumeration value="unordered"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType name="caparesponse--answer">
    <xs:annotation>
      <xs:documentation>
        Correct list of values or vectors.
      </xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded" minOccurs="0">
      <xs:element name="vector" type="caparesponse--vector"/>
      <xs:element name="value" type="caparesponse--value"/>
    </xs:choice>
    <xs:attribute name="name" type="xs:string"/>
    <xs:attribute default="ordered" name="type">
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="ordered"/>
              <xs:enumeration value="unordered"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
  </xs:complexType>
  <xs:simpleType name="caparesponse--value">
    <xs:annotation>
      <xs:documentation>Value</xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string"/>
  </xs:simpleType>
  <xs:simpleType name="caparesponse--vector">
    <xs:annotation>
      <xs:documentation>Vector</xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string"/>
  </xs:simpleType>
  <xs:annotation>
    <xs:documentation>
      Formula response (using caparesponse--answergroup like numericalresponse).
    </xs:documentation>
  </xs:annotation>
  <xs:element name="formularesponse">
    <xs:annotation>
      <xs:documentation>Query for a formula.</xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
        <xs:element name="answergroup" type="caparesponse--answergroup"/>
        <xs:element ref="hiddenline"/>
        <xs:element ref="hintgroup"/>
        <xs:element ref="formulahint"/>
        <xs:element ref="hintpart"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="response-identification"/>
      <xs:attribute name="answer" type="xs:string"/>
      <xs:attribute name="samples" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Sample Points.
            
            Format:
            1. A comma-separated list of the variables you wish to interpret,
            2. followed by “@” (not in quotes),
            3. followed by any number of the following two things, separated by semi-colons:
            (a) a comma-separated list of as many numbers as there are variables, which specifies one sampling point, OR
            (b) a comma-separated list of as many numbers as there are variables, followed by a colon, followed by another list of as many numbers as there are variables, followed by a #, followed by an integer.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="preprocess" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Pre-Processor Subroutine
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  
  <xs:annotation>
    <xs:documentation>
      Math response
    </xs:documentation>
  </xs:annotation>
  <xs:element name="mathresponse">
    <xs:annotation>
      <xs:documentation>
        Query for text that is evaluated with a script written in a computer algebra system language by the problem author.
        
        MathResponse is extremely powerful, as it tests answers for conditions rather than agreement with a particular correct answer. An unfortunate byproduct, however, is that it cannot be analyzed by several of the LON-CAPA statistics tools.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
        <xs:element name="answer" type="mathresponse--answer">
          <xs:annotation>
            <xs:documentation>
              Maxima or R script using the arrays RESPONSE and LONCAPALIST.
            </xs:documentation>
          </xs:annotation>
        </xs:element>
        <xs:element ref="textfield"/>
        <xs:element ref="hiddensubmission"/>
        <xs:element ref="hiddenline"/>
        <xs:element ref="hintgroup"/>
        <xs:element ref="mathhint"/>
        <xs:element ref="hintpart"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="response-identification"/>
      <xs:attribute name="answerdisplay" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            String to display for answer
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="cas" use="required">
        <xs:annotation>
          <xs:documentation>
            Algebra System. Maxima and R are supported.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="maxima"/>
                <xs:enumeration value="R"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="args" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Argument Array
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="libraries" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:complexType mixed="true" name="mathresponse--answer">
    <xs:annotation>
      <xs:documentation>Answer algorithm</xs:documentation>
    </xs:annotation>
    <xs:attribute name="type" type="xs:string"/>
    <xs:attribute fixed="preserve" ref="xml:space"/>
  </xs:complexType>
  
  <xs:annotation>
    <xs:documentation>
      Function plot response
    </xs:documentation>
  </xs:annotation>
  <xs:element name="functionplotresponse">
    <xs:annotation>
      <xs:documentation>
        Query for the drawing of a function.
        
        Requires that the student creates a plot that matches specified criteria.
        Examples can be functions that have certain slopes, curvature, maxima or minima at specified independent coordinate values. The students create their answer by dragging the curves and adjusting the slopes.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="functionplotelements"/>
        <xs:element ref="functionplotruleset"/>
        <xs:element ref="hintgroup"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="response-identification"/>
      <xs:attribute name="width" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            Width (pixels)
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="height" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            Height (pixels)
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="xlabel" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Label x-axis
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="-10" name="xmin" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Minimum x-value
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="10" name="xmax" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Maximum x-value
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="yes" name="xaxisvisible" type="yesno-or-perl">
        <xs:annotation>
          <xs:documentation>
            x-axis visible
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="ylabel" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Label y-axis
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="-10" name="ymin" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Minimum y-value
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="10" name="ymax" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Maximum y-value
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="yes" name="yaxisvisible" type="yesno-or-perl">
        <xs:annotation>
          <xs:documentation>
            y-axis visible
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="yes" name="gridvisible" type="yesno-or-perl">
        <xs:annotation>
          <xs:documentation>
            This determines whether or not the grid is on the graph.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="answerdisplay" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Background plot(s) for answer (function(x):xmin:xmax,function(x):xmin:xmax,x1:y1:sx1:sy1:x2:y2:sx2:sy2,...)
            
            This is a green curve the computer will display once the correct answer has been submitted. It is static, and can be given as a piecewise function.
            Since some problems will have multiple correct answers, this necessarily will only be a possible answer. Only the left hand side of the equation is necessary. For example, entering x + 2 will display the line y = x + 2.
            The syntax must be syntax recognized by GeoGebra. To test syntax for Geogebra directly, visit http://www.geogebra.org/webstart/geogebra.html .
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="functionplotelements">
    <xs:annotation>
      <xs:documentation>Function Plot Elements</xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="spline"/>
        <xs:element ref="backgroundplot"/>
        <xs:element ref="plotobject"/>
        <xs:element ref="plotvector"/>
        <xs:element ref="drawvectorsum"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="spline">
    <xs:annotation>
      <xs:documentation>
        At least one spline is necessary for a graph problem. These splines are what will be adjusted and analyzed to solve the problem.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="index">
        <xs:annotation>
          <xs:documentation>
            This is the label assigned to the spline. In general, it's simplest just to label them A, B, C etc.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:pattern value="[a-zA-Z_]+"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="order">
        <xs:annotation>
          <xs:documentation>
            This determines the number of Control Points on the spline. For example, selecting '3' means there will be 3 points on the spline that can be moved, as well as 3 points off the spline that will control the slope. 
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:int">
                <xs:minInclusive value="2"/>
                <xs:maxInclusive value="8"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="initx" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Initial x-value
            "Initial x-value" and "Initial y-value" determine where the left most Control Point will be.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="inity" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Initial y-value
            "Initial x-value" and "Initial y-value" determine where the left most Control Point will be.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="scalex" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            This determines the right most location of the Control Points (on the spline). To figure out where this point will be, add 'Initial x-value' to 'Scale x'.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="scaley" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            This determines the distance (in the y-direction) between the Control Points on the spline, and the ones that control the slope.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="backgroundplot">
    <xs:annotation>
      <xs:documentation>
        Background Function Plot.
        This places a static curve on the graph of your choosing. It can be labeled, moveable or fixed, and any color desired. Only the right hand side of the function you want displayed is necessary. For example, entering x+2 will display the line y=x+2. The syntax must be syntax recognized by GeoGebra.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="function" type="xs:string" use="required">
        <xs:annotation>
          <xs:documentation>
            An equals sign is not necessary. Just give the right hand side of the function. LON-CAPA variables are usable as well to allow individualized problems for each student. The syntax must be syntax recognized by GeoGebra.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="xinitial" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Initial x-value (optional)
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="xfinal" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Final x-value (optional)
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="label" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Label on Plot
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="color">
        <xs:annotation>
          <xs:documentation>
            Color of the background function (hex code).
            The default is 000000 (black). It is recommended to choose a color other than green, since it is easily confused with being the answer.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:pattern value="[\da-fA-F]{6}"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute default="yes" name="fixed" type="yesno-or-perl">
        <xs:annotation>
          <xs:documentation>
            Fixed location
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="plotobject">
    <xs:annotation>
      <xs:documentation>
        This places a point in the applet. Generally intended to be used with Vectors to create problems involving Free-Body Diagrams or any other points that vectors (or arrows) connect to and from.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="label" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Label on Plot
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="x" type="real-or-perl"/>
      <xs:attribute name="y" type="real-or-perl"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="plotvector">
    <xs:annotation>
      <xs:documentation>
        This creates a vector (or arrow) in the applet. Generally intended to be used with Objects to create problems involving Free-Body Diagrams or to establish connections between Objects.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="label" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Label on Plot.
            Determines the name of the vector, as well as the name that will be visible in the problem.
            This value MUST be capitalized and cannot include spaces or most symbols. To be safe, stick with letters and numbers.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="tailx" type="real-or-perl"/>
      <xs:attribute name="taily" type="real-or-perl"/>
      <xs:attribute name="tipx" type="real-or-perl"/>
      <xs:attribute name="tipy" type="real-or-perl"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="drawvectorsum">
    <xs:annotation>
      <xs:documentation>Draw Vector Sum</xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="label" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Label on Plot
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="tailx" type="real-or-perl"/>
      <xs:attribute name="taily" type="real-or-perl"/>
      <xs:attribute default="yes" name="showvalue" type="yesno-or-perl"/>
      <xs:attribute name="vectorlist" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="functionplotruleset">
    <xs:annotation>
      <xs:documentation>
        This is where the rules are defined. These rules will determine whether or not an entered answer is correct or not. If there are no rules, any answer will be deemed correct. If there is more than one rule, when an answer is submitted, the server will analyze them in order until one of them is broken (of course, if it's a correct answer, it will go through all of them and return a green box). In such an event, any subsequent rules will be ignored. If conditional hints related to these rules are added, only the first broken rule's hint will be shown, even if all rules are broken.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="functionplotrule"/>
        <xs:element ref="functionplotvectorrule"/>
        <xs:element ref="functionplotvectorsumrule"/>
        <xs:element ref="functionplotcustomrule"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="functionplotrule">
    <xs:annotation>
      <xs:documentation>
        Function Plot Graph Rule.
        
        Used to create a rule that determines whether or not a submitted graph is correct. In general, it takes the form of testing the function, its integral, or its first or second derivative over a given set of x-values. The test can be to see if it equals, is greater than, or less than a specified value. Anywhere a number is needed, a variable can also be used.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="index" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            This is an internal label for the rule. Something must be entered here, and it must be different for each rule. This same value will be used to add a conditional hint.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="0" name="derivativeorder">
        <xs:annotation>
          <xs:documentation>
            This determines what the server will be testing. For instance, choose 'First derivative' causes the server to evaluate the derivative of the entered answer over the given domain.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:int">
                <xs:enumeration value="0"/>
                <xs:enumeration value="1"/>
                <xs:enumeration value="2"/>
                <xs:enumeration value="-1"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="xinitial" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Initial x-value.
            A value must be entered for one of "Initial x-value" and "Initial x-value label". Either choose a numerical value for x (the first option), or choose the beginning of the submitted answer, the end, or a previously chosen named point.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="xinitiallabel">
        <xs:annotation>
          <xs:documentation>
            Initial x-value label.
            A value must be entered for one of "Initial x-value" and "Initial x-value label". Either choose a numerical value for x (the first option), or choose the beginning of the submitted answer, the end, or a previously chosen named point.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:pattern value="[a-zA-Z_]+"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="xfinal" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Final x-value (optional).
            This determines the end of the domain over which the rule examines. To test only a single point (the initial value), leave "Final x-value" and "Final x-value label" blank. If a label is entered, such as 'positive', the point at which the rule fails will be given this special label. This label can then be used in subsequent rules as an 'Initial x-value label'.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="xfinallabel">
        <xs:annotation>
          <xs:documentation>
            Final x-value label (optional).
            This determines the end of the domain over which the rule examines. To test only a single point (the initial value), leave "Final x-value" and "Final x-value label" blank. If a label is entered, such as 'positive', the point at which the rule fails will be given this special label. This label can then be used in subsequent rules as an 'Initial x-value label'.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:pattern value="[a-zA-Z_]+"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="minimumlength" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Minimum length for range (optional).
            This tests that the difference between the initial and final x-values are at least a certain length apart. This is only useful if there is at least one label.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="maximumlength" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Maximum length for range (optional).
            This tests that the difference between the initial and final x-values are at most a certain length apart. This is only useful if there is at least one label.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="eq" name="relationship">
        <xs:annotation>
          <xs:documentation>
            The heart of the rule. This choice determines whether the chosen 'function' is greater than, less than, equal to, etc. a certain 'value'.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="eq"/>
                <xs:enumeration value="ne"/>
                <xs:enumeration value="ge"/>
                <xs:enumeration value="gt"/>
                <xs:enumeration value="lt"/>
                <xs:enumeration value="le"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute default="undef" name="value" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Enter the number you wish to compare to. It is also possible to choose 'not defined', in the event the answer should not have a value for the given domain. Within the value argument, the function itself can be evaluated using &fpr_f(), its derivative using &fpr_dfdx(), and its second derivative using &fpr_d2fdx2(). This allows for a comparison of two points on the graph. The value of a previously defined label can be retrieved using the function &fpr_val(), e.g., &fpr_val('positive'). Previous defined values from script blocks can also be retrieved as normal variables, e.g., $x.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="percenterror" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            This allows for a margin of error in the y-direction. For instance, if the rule requires that the derivative be equal to 5, the server will accept values close enough to 5 that are within the percent error defined here. Note: Choosing 10% would not mean that the answer is correct as long as it is within the range 4.5-5.5. Instead, the percent corresponds to the total size of the graph. For the function itself, the 'percent error' is multiplied by the ymax-ymin; for the first derivative, it's multiplied by (ymax-ymin)/(xmax-xmin); for the second derivative, it's multiplied by (ymax-ymin)/(xmax-xmin)2; and for the integral, it's multiplied by (ymax-ymin)*(xmax-xmin).
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="functionplotvectorrule">
    <xs:annotation>
      <xs:documentation>
        Function Plot Vector Rule
        Used to test whether vectors are in the right place, pointed in the right direction, and have the correct length.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="index" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Index/Name
            This is an internal label for the rule. This attribute must be different for each rule. This same value will be used to add a conditional hint.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="vector" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            The name of one of the vectors in the list of function plot elements. Specifically, the one you want to test.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="attachpoint" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Attached to object.
            Object(s) this vector should be attached to. For more than one object, separate them by commas. If the vector should not be attached to any object, leave this blank. In this case, an object is considered attached if its tail OR its tip is in the vicinity of the object.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="notattachpoint" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Not attached to object.
            Object(s) that this vector should be not be near.
            For more than one object, separate them by commas. Particularly useful for distractor vectors.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="tailpoint" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Tail attached to object.
            Tail(s) this vector should be attached to.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="tippoint" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Tip attached to object.
            Tip(s) this vector should be attached to.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="nottailpoint" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Tail not attached to object.
            Tail(s) this vector should not be attached to.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="nottippoint" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Tip not attached to object.
            Tip(s) this vector should not be attached to.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="length" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            How long the vector should be.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="lengtherror" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Absolute error length.
            How accurate the length must be to get the answer correct.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="angle" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            What direction should the vector point. All values are measured in degrees, counterclockwise starting at the positive x-axis. Values must be 0 ≤ θ < 360.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="angleerror" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Absolute error angle.
            How accurate the angle must be to get the answer correct.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="functionplotvectorsumrule">
    <xs:annotation>
      <xs:documentation>
        Function Plot Vector Sum Rule
        Used to test the sum of a set of vectors.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="index" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Index/Name.
            This is an internal label for the rule. It must be different for each rule. This same value will be used to add a conditional hint.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="vectors" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Comma-separated list of vectors.
            List all of the vectors that should be added up to be tested.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="length" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Sum vector length.
            How long the sum of these vectors should be.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="lengtherror" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Absolute error length.
            How accurate the length must be to get the answer correct.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="angle" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Sum vector angle.
            What direction should the sum of these vectors point. All values are measured in degrees, counterclockwise starting at the positive x-axis. Values must be 0 ≤ θ < 360.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="angleerror" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Absolute error angle.
            How accurate the angle must be to get the answer correct.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="functionplotcustomrule">
    <xs:annotation>
      <xs:documentation>
        Used to create rules that aren't options using the other rules. The coding is done in Perl and follows Perl syntax. Any variable written inside this rule will be recognized as normal and any evaluation function can be used as well.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element name="answer">
          <xs:annotation>
            <xs:documentation>
              Answer algorithm, normally in Perl
            </xs:documentation>
          </xs:annotation>
          <xs:complexType mixed="true">
            <xs:attribute default="loncapa/perl" name="type" type="xs:string"/>
            <xs:attribute fixed="preserve" ref="xml:space"/>
          </xs:complexType>
        </xs:element>
      </xs:choice>
      <xs:attribute name="index" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Index/Name
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  
  <xs:annotation>
    <xs:documentation>
      Organic response
    </xs:documentation>
  </xs:annotation>
  <xs:element name="organicresponse">
    <xs:annotation>
      <xs:documentation>
        Query for an organic chemical structure with a molecular editor.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
        <xs:element ref="hiddenline"/>
        <xs:element ref="hintgroup"/>
        <xs:element ref="organichint"/>
        <xs:element ref="hintpart"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="response-identification"/>
      <xs:attribute default="autoez" name="options">
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:pattern value="(autoez|multipart|nostereo|reaction|number)(\s*,\s*(autoez|multipart|nostereo|reaction|number))*"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="molecule" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Starting Molecule
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="answer" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Correct Answer
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="jmeanswer" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            JME string of the answer
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="width" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            Width of correct answer image
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  
  <xs:annotation>
    <xs:documentation>
      Reaction response
    </xs:documentation>
  </xs:annotation>
  <xs:element name="reactionresponse">
    <xs:annotation>
      <xs:documentation>
        Query for a chemical reaction.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
        <xs:element ref="hiddenline"/>
        <xs:element ref="hintgroup"/>
        <xs:element ref="reactionhint"/>
        <xs:element ref="hintpart"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="response-identification"/>
      <xs:attribute name="answer" type="xs:string" use="required"/>
      <xs:attribute name="initial" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Initial Reaction
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  
  <xs:annotation>
    <xs:documentation>
      Custom response
    </xs:documentation>
  </xs:annotation>
  <xs:element name="customresponse">
    <xs:annotation>
      <xs:documentation>
        Query for text without any constraint (any character is allowed). A script analyzes the answer to grade it automatically.
        The use of this response type is generally discouraged, since the responses will not be analyzable by the LON-CAPA statistics tools.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
        <xs:element name="answer" type="customresponse--answer"/>
        <xs:element ref="textfield"/>
        <xs:element ref="hiddensubmission"/>
        <xs:element ref="hiddenline"/>
        <xs:element ref="hintgroup"/>
        <xs:element ref="customhint"/>
        <xs:element ref="hintpart"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="response-identification"/>
      <xs:attribute name="answerdisplay" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            String to display for answer
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:complexType mixed="true" name="customresponse--answer">
    <xs:annotation>
      <xs:documentation>
        Perl script evaluating the student answer.
        
        For a single textfield, the student’s answer will be in a variable $submission. If the Custom Response has multiple textfields, the answers will be in an array reference, and can be accessed as $$submission[0], $$submission[1], etc.
        
        The script must return a standard LON-CAPA response. The most common LON-CAPA responses are:
        - EXACT ANS: return if solved exactly correctly
        - APPROX ANS: return if solved approximately
        - INCORRECT: return if not correct, uses up a try
        - ASSIGNED SCORE: partial credit (also return the credit factor, e.g. return(ASSIGNED SCORE,0.3);)
        - SIG FAIL, NO UNIT, EXTRA ANSWER, MISSING ANSWER, BAD FORMULA, WANTED NUMERIC, WRONG FORMAT: return if not correct for different reasons, does not use up a try
      </xs:documentation>
    </xs:annotation>
    <xs:attribute name="type" type="xs:string"/>
    <xs:attribute fixed="preserve" ref="xml:space"/>
  </xs:complexType>
  
  <xs:annotation>
    <xs:documentation>
      External response
    </xs:documentation>
  </xs:annotation>
  <xs:element name="externalresponse">
    <xs:annotation>
      <xs:documentation>
        Query for a long text or a line, sent to an external program for grading.
        
        The form sent will consist of:
        - LONCAPA student response full text of what the student entered in the entry field
        - LONCAPA correct answer contents of the answer attribute
        - LONCAPA language specified language encoding of the requesting resource
        - all items in the form attribute if any of these clash with the above, the above values will overwite the value in the form attribute
        
        The response of the remote server needs to be in XML as follows:
        - loncapagrade: takes no attributes, but must surround the response.
        - awarddetail: required. The delimited text inside must be one of the detailed results that appears in the data storage documentation. CVS:loncapa/doc/homework/datastorage, look for resource.partid.responseid.awarddetail.
        - message: optional message to have shown to the student.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="textfield"/>
        <xs:element ref="hiddensubmission"/>
        <xs:element ref="hiddenline"/>
        <xs:element ref="hintgroup"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="response-identification"/>
      <xs:attribute name="url" type="xs:anyURI">
        <xs:annotation>
          <xs:documentation>
            url to submit the answer form to. It does not need to be a LON-CAPA machine.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="answer" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            data to post in the form element LONCAPA_correct_answer to the remote site.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="form" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            hash variable name that will be submitted to the remote site as a HTTP form.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="answerdisplay" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  
  <xs:annotation>
    <xs:documentation>
      Data response
    </xs:documentation>
  </xs:annotation>
  <xs:element name="dataresponse">
    <xs:annotation>
      <xs:documentation>
        Query for text or numbers.
        Advanced type of response that implements a simple data storage and needs an input tag, such as textline, to work correctly.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="textfield"/>
        <xs:element ref="hiddensubmission"/>
        <xs:element ref="hiddenline"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="response-identification"/>
      <xs:attribute name="type" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            type of data stored in this response field. It should be one of the types supported by parameter.html
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="display" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            string that will be used to describe the field when interfacing with humans.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  
  <xs:annotation>
    <xs:documentation>
      Shared response elements
    </xs:documentation>
  </xs:annotation>
  <xs:element name="responseparam">
    <xs:annotation>
      <xs:documentation>
        Parameters for a response
        
        Defines an externally adjustable parameter for the question, which the question can then use to allow other users to customize the problem for their courses without changing the source code of the problem.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="name" type="xs:string"/>
      <xs:attribute name="type" type="xs:string" use="required"/>
      <xs:attribute name="default" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            default value
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="description" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="textfield">
    <xs:annotation>
      <xs:documentation>
        Large Text Entry Area, contains the text that appears by default
        
        Creates a large text input box. If data appears between the start and end tags, the data will appear in the textfield if the student has not yet made a submission.
        Additionally, it takes two attributes: rows and cols, which control the height and width of the text area respectively.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="tex"/>
        <xs:element ref="web"/>
      </xs:choice>
      <xs:attribute default="10" name="rows" type="int-or-perl"/>
      <xs:attribute default="80" name="cols" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            Columns
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="addchars" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Click-On Texts (comma sep)
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="spellcheck">
        <xs:annotation>
          <xs:documentation>
            Spellcheck for
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="none"/>
                <xs:enumeration value="en"/>
                <xs:enumeration value="de"/>
                <xs:enumeration value="he"/>
                <xs:enumeration value="es"/>
                <xs:enumeration value="fr"/>
                <xs:enumeration value="pt"/>
                <xs:enumeration value="tr"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="textline">
    <xs:annotation>
      <xs:documentation>
        Single Line Text Entry Area. Displays a field to enter text for a response.
        Should only be used inside stringresponse, numericalresponse, formularesponse, mathresponse, organicresponse, reactionresponse and customresponse.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute default="20" name="size" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            controls the width of the textline
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="addchars" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Comma-separated list of characters or words that can be inserted with a click.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="no" name="readonly" type="yesno-or-perl"/>
      <xs:attribute name="spellcheck">
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="none"/>
                <xs:enumeration value="en"/>
                <xs:enumeration value="de"/>
                <xs:enumeration value="he"/>
                <xs:enumeration value="es"/>
                <xs:enumeration value="fr"/>
                <xs:enumeration value="pt"/>
                <xs:enumeration value="tr"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="hiddensubmission">
    <xs:annotation>
      <xs:documentation>
        This creates a hidden form field with the given value.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="value" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="hiddenline">
    <xs:annotation>
      <xs:documentation>
        This creates a hidden form field with the old response value.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
    </xs:complexType>
  </xs:element>
  
  <xs:annotation>
    <xs:documentation>
      Hints
    </xs:documentation>
  </xs:annotation>
  <xs:element name="hintgroup">
    <xs:annotation>
      <xs:documentation>
        The first part of the hint is the condition, which includes a specification of the foil(s) and foil answer(s) required to trigger the hint. The answers specified in the hint condition are compared with the user's submission, and if the condition is met, the hint action included in the conditional hint block will be executed (for example this could be the display of a block of text). You can set multiple hint conditions for a particular problem. Hint conditions are identified by a name. The corresponding hint action includes this hint condition name in the "on" parameter. When a hint condition evaluates to true, the corresponding hint action is triggered. Besides providing hint actions within <hintpart on="NAME"> </hintpart> tags for each named (NAME) hint condition, a hint can be designated for display if none of the conditional hints evaluate to true. The default hint is not displayed if the conditions were met for any of the conditional hints. The defau!
 lt hint action is included between <hintpart on="default"> </hintpart> tags.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
        <xs:element ref="hintpart"/>
        <xs:element ref="stringhint"/>
        <xs:element ref="radiobuttonhint"/>
        <xs:element ref="optionhint"/>
        <xs:element ref="numericalhint"/>
        <xs:element ref="formulahint"/>
        <xs:element ref="mathhint"/>
        <xs:element ref="organichint"/>
        <xs:element ref="reactionhint"/>
        <xs:element ref="customhint"/>
      </xs:choice>
      <xs:attribute default="no" name="showoncorrect" type="yesno-or-perl">
        <xs:annotation>
          <xs:documentation>
            Show hint even if problem Correct
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="hintpart">
    <xs:annotation>
      <xs:documentation>
        Conditional Hint
        
        When a hint tag named the same as the on attribute evaluates to be correct, the hintpart will show.
        If no other hintpart is to show then all hintparts with an on value set to “default” will show.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
        <xs:element ref="parserlib"/>
        <xs:element ref="scriptlib"/>
      </xs:choice>
      <xs:attribute name="on" type="xs:string" use="required"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="stringhint">
    <xs:annotation>
      <xs:documentation>
        String Hint Condition
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
      </xs:choice>
      <xs:attribute name="id" type="xs:string"/>
      <xs:attribute name="name" type="xs:string" use="required">
        <xs:annotation>
          <xs:documentation>
            Name of the hint condition.
            Should be set to the value of which hintpart will be shown.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="answer" type="xs:string" use="required">
        <xs:annotation>
          <xs:documentation>
            Text string.
            Should be set to the value of which hintpart will be shown.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="cs" name="type">
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="cs"/>
                <xs:enumeration value="ci"/>
                <xs:enumeration value="mc"/>
                <xs:enumeration value="re"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="preprocess" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Pre-Processor Subroutine
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="radiobuttonhint">
    <xs:annotation>
      <xs:documentation>
        Radiobutton Hint Condition
        
        The radiobutton hint tag takes two parameters: answer and name. The name is the name of the hint condition, and the answer is an array. The first element of the array will be 'foil'; the remaining elements are the names of the foils that you require to have been checked by the student for the hint to be displayed. For example, if you create a radiobutton response problem with six foils named: granite, gabbro, gneiss, shale, sandstone and schist, and you want your hint named: igneous to be displayed when either granite or basalt had been checked your radiobutton hint would be as follows:
        
        <radiobuttonhint answer="('foil','granite','gabbro')" name="igneous"></radiobuttonhint>
        
        In order to trigger display of this hint you also need to create a <hintpart> </hintpart> block that will include a textblock that contains the text of the actual hint.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
      </xs:choice>
      <xs:attribute name="id" type="xs:string"/>
      <xs:attribute name="name" type="xs:string" use="required">
        <xs:annotation>
          <xs:documentation>
            should be set to the value of which hintpart will be shown
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="answer" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            should be at least a two element list: first the type (foil or concept) and then either the foil name(s) or the concept string(s), e.g., “(’foil’,’greaterthan’,’equal’)” if the condition should be triggered by the foils named “greaterthan” or “equal”
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="optionhint">
    <xs:annotation>
      <xs:documentation>
        Option Response Hint Condition
        
        There are two types of option response hint conditions: one for standalone foils and one for concept groups. In both cases the option hint tag includes two parameters: answer and name for standalone foils, and concept and name for foils grouped together in a concept group. For the answer parameter, the names and submitted values for each of the foils that are being included in the hint condition are provided in a hash, i.e., in the format: ('Foil1'= > 'True','Foil2'= > 'False'). In the case of a conditional hint for a concept group, the format of the concept parameter is also a hash that links the name of each concept group included in the hint condition to either 'correct' or 'incorrect' - e.g., < optionhint concept="('buoyancy'= > 'correct','density'= > 'correct')" name="fluids" / > If 'correct' is specified for a named concept then when the conditional hint is evaluated answers for each of the foils selected by a student must be correct for the h!
 int action to be triggered. If anything other than 'correct' is provided in the concept hash in the optionhint tag then then students answers will be compared with the set answers for the foils in the concept group and as long as at least one answer is incorrect (i.e., the concept group was not correctly answered) then the corresponding hint action will be triggered. 
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
      </xs:choice>
      <xs:attribute name="id" type="xs:string"/>
      <xs:attribute name="name" type="xs:string" use="required">
        <xs:annotation>
          <xs:documentation>
            should be set to the value of which hintpart will be shown
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="answer" type="xs:string"/>
      <xs:attribute name="concept" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="numericalhint">
    <xs:annotation>
      <xs:documentation>
        Numerical Hint Condition
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
      </xs:choice>
      <xs:attribute name="id" type="xs:string"/>
      <xs:attribute name="name" type="xs:string" use="required">
        <xs:annotation>
          <xs:documentation>
            Unique name given to the hint condition.
            Should be set to the value of which hintpart will be shown.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="answer" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Numerical answer for which the conditional is provided.
            Student submission of that answer in combination with the "unit" attribute in the hint condition will trigger the hint action specified in the <hintpart> element.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="unit" type="xs:string"/>
      <xs:attribute name="format" type="xs:string"/>
      <xs:attribute name="preprocess" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Pre-Processor Subroutine
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="formulahint">
    <xs:annotation>
      <xs:documentation>
        Formula Hint Condition
        
        The formula submitted by the student is evaluated at the sample points for the hint and the calculated values are compared with the corresponding values determined by evaluating the "hint" answer at the same sampling points. A close correspondence between the two sets of values will trigger the hint action specified in the <hintpart> element.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
      </xs:choice>
      <xs:attribute name="id" type="xs:string"/>
      <xs:attribute name="name" type="xs:string" use="required">
        <xs:annotation>
          <xs:documentation>
            Unique name given to the hint condition.
            Should be set to the value of which hintpart will be shown.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="answer" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Formula answer for which the conditional is provided.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="samples" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Sample points (or range of points) over which sampling of the student’s submitted answer and the formula included in the formula hint answer parameter are to be compared. The syntax is the same as used to specify sampling points in the samples
            parameter of the formula reponse element itself.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="preprocess" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Pre-Processor Subroutine
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="mathhint">
    <xs:annotation>
      <xs:documentation>Math Hint Condition</xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
        <xs:element name="answer" type="mathhint--answer"/>
      </xs:choice>
      <xs:attribute name="id" type="xs:string"/>
      <xs:attribute name="name" type="xs:string"/>
      <xs:attribute default="maxima" name="cas">
        <xs:annotation>
          <xs:documentation>
            Algebra System
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="maxima"/>
                <xs:enumeration value="R"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="args" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Argument Array
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="organichint">
    <xs:annotation>
      <xs:documentation>Organic Hint Condition</xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
      </xs:choice>
      <xs:attribute name="id" type="xs:string"/>
      <xs:attribute name="name" type="xs:string"/>
      <xs:attribute name="answer" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="reactionhint">
    <xs:annotation>
      <xs:documentation>Reaction Hint Condition</xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
      </xs:choice>
      <xs:attribute name="id" type="xs:string"/>
      <xs:attribute name="name" type="xs:string"/>
      <xs:attribute name="answer" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="customhint">
    <xs:annotation>
      <xs:documentation>
        Custom Hint Condition
        
        Define the hint condition within an answer block inside of the customhint block. The condition is defined like how an answer is defined in customresponse where you need to return EXACT ANS to indicate when customhint criteria are met.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="responseparam"/>
        <xs:element name="answer" type="customhint--answer"/>
      </xs:choice>
      <xs:attribute name="id" type="xs:string"/>
      <xs:attribute name="name" type="xs:string" use="required">
        <xs:annotation>
          <xs:documentation>
            should be set to the value of which hintpart will be shown
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:complexType mixed="true" name="customhint--answer">
    <xs:annotation>
      <xs:documentation>Hint algorithm</xs:documentation>
    </xs:annotation>
    <xs:attribute name="type" type="xs:string"/>
    <xs:attribute fixed="preserve" ref="xml:space"/>
  </xs:complexType>
  <xs:complexType mixed="true" name="mathhint--answer">
    <xs:annotation>
      <xs:documentation>Hint algorithm</xs:documentation>
    </xs:annotation>
    <xs:attribute name="type" type="xs:string"/>
    <xs:attribute fixed="preserve" ref="xml:space"/>
  </xs:complexType>
  
  <xs:annotation>
    <xs:documentation>
      Random label
    </xs:documentation>
  </xs:annotation>
  <xs:element name="randomlabel">
    <xs:annotation>
      <xs:documentation>
        Randomly labeled image
        
        This shows a specified image with images or text labels randomly assigned to a set of specific locations. Those locations may also have values assigned to them. A hash is generated that contains the mapping of labels to locations, labels to values, and locations to values.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element name="labelgroup" type="randomlabel--labelgroup">
          <xs:annotation>
            <xs:documentation>
              One is required, but multiple are allowed. This declares a group of locations and labels associated with them.
            </xs:documentation>
          </xs:annotation>
        </xs:element>
        <xs:element name="bgimg">
          <xs:annotation>
            <xs:documentation>
              Element alternative to the bgimg attribute, which makes it possible to use a plot as a background image.
            </xs:documentation>
          </xs:annotation>
          <xs:complexType mixed="true">
            <xs:choice maxOccurs="unbounded" minOccurs="0">
              <xs:element ref="gnuplot"/>
            </xs:choice>
          </xs:complexType>
        </xs:element>
      </xs:choice>
      <xs:attribute name="bgimg" type="xs:anyURI">
        <xs:annotation>
          <xs:documentation>
            Either a fully qualified URL for an external image or a LON-CAPA resource. It supports relative references (../images/apicture.gif). The image must either be a GIF or JPEG.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="width" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            The width of the image in pixels.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="height" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            The height of the image in pixels.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="texwidth" type="decimal-or-perl">
        <xs:annotation>
          <xs:documentation>
            The width of the image in millimeters.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:complexType name="randomlabel--labelgroup">
    <xs:annotation>
      <xs:documentation>Group of Labels</xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded">
      <xs:element name="label" type="randomlabel--label"/>
      <xs:element name="location" type="randomlabel--location">
        <xs:annotation>
          <xs:documentation>
            declares a location on the image that a label should appear at
          </xs:documentation>
        </xs:annotation>
      </xs:element>
    </xs:choice>
    <xs:attribute name="name" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          This is the name of the group.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="text" name="type">
      <xs:annotation>
        <xs:documentation>
          the type of labels in this group
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="text"/>
              <xs:enumeration value="image"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute default="\normalsize" name="TeXsize">
      <xs:annotation>
        <xs:documentation>
          TeX font size
          Warning: as opposed to the TeXsize attribute in <h1>..<h6> <font> and <basefont>, this one requires a \ at the beginning of the values.
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="\tiny"/>
              <xs:enumeration value="\scriptsize"/>
              <xs:enumeration value="\footnotesize"/>
              <xs:enumeration value="\small"/>
              <xs:enumeration value="\normalsize"/>
              <xs:enumeration value="\large"/>
              <xs:enumeration value="\Large"/>
              <xs:enumeration value="\LARGE"/>
              <xs:enumeration value="\huge"/>
              <xs:enumeration value="\Huge"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType mixed="true" name="randomlabel--label">
    <xs:annotation>
      <xs:documentation>Label Text or Path to image</xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded" minOccurs="0">
      <xs:group ref="text-only"/>
      <xs:element ref="preduedate"/>
      <xs:element ref="postanswerdate"/>
      <xs:element ref="parserlib"/>
      <xs:element ref="scriptlib"/>
    </xs:choice>
    <xs:attribute name="description" type="xs:string"/>
  </xs:complexType>
  <xs:complexType name="randomlabel--location">
    <xs:annotation>
      <xs:documentation>Label Location</xs:documentation>
    </xs:annotation>
    <xs:attribute name="x" type="int-or-perl" use="required"/>
    <xs:attribute name="y" type="int-or-perl" use="required"/>
    <xs:attribute name="value" type="xs:string"/>
  </xs:complexType>
  
  <xs:annotation>
    <xs:documentation>
      Gnuplot
    </xs:documentation>
  </xs:annotation>
  <xs:element name="gnuplot">
    <xs:annotation>
      <xs:documentation>
        The gnuplot LON-CAPA tag allows an author to design a plot which will be created programatically at the time when it is requested for display by a student. This is intended for use in homework problems where a distinct plot should be rendered for each student. It can be used in conjunction with a script to generate curve data for random plots.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="gnuplot-children"/>
      </xs:choice>
      <xs:attribute default="dynamically generated plot" name="alttag" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Brief description of the plot.
            This text is used as the alt value of the img tag used to display the plot on a web page.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="300" name="height" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            Height of image (pixels)
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="400" name="width" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            Width of image (pixels)
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="xffffff" name="bgcolor" type="color-or-perl">
        <xs:annotation>
          <xs:documentation>
            Background color of image
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="x000000" name="fgcolor" type="color-or-perl">
        <xs:annotation>
          <xs:documentation>
            Foreground color of image
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="off" name="transparent" type="onoff-or-perl">
        <xs:annotation>
          <xs:documentation>
            Transparent image. If the image is transparent the background color will be ignored.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="on" name="grid" type="onoff-or-perl">
        <xs:annotation>
          <xs:documentation>
            Display grid
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="off" name="gridlayer" type="onoff-or-perl">
        <xs:annotation>
          <xs:documentation>
            Display grid front layer over filled boxes or filled curves
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="noborder" name="box_border">
        <xs:annotation>
          <xs:documentation>
            Draw border for boxes
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="border"/>
                <xs:enumeration value="noborder"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute default="on" name="border" type="onoff-or-perl">
        <xs:annotation>
          <xs:documentation>
            Draw border around plot
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="9" name="font">
        <xs:annotation>
          <xs:documentation>
            Font size to use in web output (in pts, or "small", "medium" or "large").
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:int">
                <xs:minInclusive value="5"/>
                <xs:maxInclusive value="15"/>
              </xs:restriction>
            </xs:simpleType>
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="small"/>
                <xs:enumeration value="medium"/>
                <xs:enumeration value="large"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute default="sans-serif" name="fontface">
        <xs:annotation>
          <xs:documentation>
            Type of font to use
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="sans-serif"/>
                <xs:enumeration value="serif"/>
                <xs:enumeration value="classic"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute default="100" name="samples">
        <xs:annotation>
          <xs:documentation>
            Number of samples for non-data plots.
            If a function element is used to specify the curve, this indicates the number of sample points to use.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:int">
                <xs:minInclusive value="100"/>
                <xs:maxInclusive value="5000"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute default="middle" name="align">
        <xs:annotation>
          <xs:documentation>
            Alignment for image in HTML
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="left"/>
                <xs:enumeration value="right"/>
                <xs:enumeration value="middle"/>
                <xs:enumeration value="center"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute default="93" name="texwidth" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            Width of plot when printed (mm)
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="22" name="texfont">
        <xs:annotation>
          <xs:documentation>
            Font size to use in TeX output (pts)
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:int">
                <xs:minInclusive value="8"/>
                <xs:maxInclusive value="36"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute default="monochrome" name="plotcolor">
        <xs:annotation>
          <xs:documentation>
            Color setting for printing
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="monochrome"/>
                <xs:enumeration value="color"/>
                <xs:enumeration value="colour"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="pattern">
        <xs:annotation>
          <xs:documentation>
            Pattern value for boxes
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:int">
                <xs:minInclusive value="0"/>
                <xs:maxInclusive value="6"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute default="0" name="solid" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            The density of fill style for boxes
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="empty" name="fillstyle">
        <xs:annotation>
          <xs:documentation>
            Filled style for boxes
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="empty"/>
                <xs:enumeration value="solid"/>
                <xs:enumeration value="pattern"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute default="Cartesian" name="plottype">
        <xs:annotation>
          <xs:documentation>
            Plot type
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="Cartesian"/>
                <xs:enumeration value="Polar"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute default="Cartesian" name="gridtype">
        <xs:annotation>
          <xs:documentation>
            Grid type
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="Cartesian"/>
                <xs:enumeration value="Polar"/>
                <xs:enumeration value="Linear-Log"/>
                <xs:enumeration value="Log-Linear"/>
                <xs:enumeration value="Log-Log"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute default="default" name="lmargin">
        <xs:annotation>
          <xs:documentation>
            Left margin width (pts)
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl xs:int">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="default"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute default="default" name="rmargin">
        <xs:annotation>
          <xs:documentation>
            Right margin width (pts)
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl xs:int">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="default"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute default="default" name="tmargin">
        <xs:annotation>
          <xs:documentation>
            Top margin width (pts)
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl xs:int">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="default"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute default="default" name="bmargin">
        <xs:annotation>
          <xs:documentation>
            Bottom margin width (pts)
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl xs:int">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="default"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="boxwidth" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Width of boxes, default is auto
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="1" name="major_ticscale" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Size of major tic marks (plot coordinates)
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="0.5" name="minor_ticscale" type="real-or-perl">
        <xs:annotation>
          <xs:documentation>
            Size of minor tic mark (plot coordinates)
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:group name="gnuplot-children">
    <xs:annotation>
      <xs:documentation>
        List of children, used in gnuplot and lonplot--block
      </xs:documentation>
    </xs:annotation>
    <xs:choice>
      <xs:element name="title" type="lonplot--title"/>
      <xs:element name="axis" type="lonplot--axis">
        <xs:annotation>
          <xs:documentation>
            The Plot Axes tag allows you to specify the domain and range of the data to display. It is closely tied with the Plot Ticks tags, which specify where the gridlines are drawn on the plot.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="curve" type="lonplot--curve">
        <xs:annotation>
          <xs:documentation>
            The curve tag is where you set the data to be plotted by gnuplot.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="key" type="lonplot--key">
        <xs:annotation>
          <xs:documentation>
            The key tag causes a key to be drawn on the plot when it is generated. The key will contain an entry for each curve which has a name.
            The key is the color of the foreground of the plot, specified in the gnuplot tag.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="label" type="lonplot--label">
        <xs:annotation>
          <xs:documentation>
            The label tag allows the author to place text at any position on the plot. There may be many label tags on one plot and all the labels which fall within the plot will show. The color used will be to foreground color of the plot and the font will be the size specified for the plot, both of which are set in the gnuplot tag.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="xtics" type="lonplot--xtics"/>
      <xs:element name="ytics" type="lonplot--ytics"/>
      <xs:element name="xlabel" type="lonplot--xlabel"/>
      <xs:element name="ylabel" type="lonplot--ylabel"/>
      <xs:element name="block" type="lonplot--block"/>
    </xs:choice>
  </xs:group>
  <xs:simpleType name="lonplot--title">
    <xs:annotation>
      <xs:documentation>Plot Title</xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string"/>
  </xs:simpleType>
  <xs:complexType name="lonplot--axis">
    <xs:annotation>
      <xs:documentation>Plot axes</xs:documentation>
    </xs:annotation>
    <xs:attribute default="x000000" name="color" type="color-or-perl">
      <xs:annotation>
        <xs:documentation>
          Color of grid lines
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="-10.0" name="xmin" type="real-or-perl">
      <xs:annotation>
        <xs:documentation>
          Minimum x-value shown in plot
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="10.0" name="xmax" type="real-or-perl">
      <xs:annotation>
        <xs:documentation>
          Maximum x-value shown in plot
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="-10.0" name="ymin" type="real-or-perl">
      <xs:annotation>
        <xs:documentation>
          Minimum y-value shown in plot
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="10.0" name="ymax" type="real-or-perl">
      <xs:annotation>
        <xs:documentation>
          Maximum y-value shown in plot
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="on" name="xformat">
      <xs:annotation>
        <xs:documentation>
          X-axis number formatting
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="on"/>
              <xs:enumeration value="off"/>
              <xs:enumeration value="2e"/>
              <xs:enumeration value="2f"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute default="on" name="yformat">
      <xs:annotation>
        <xs:documentation>
          Y-axis number formatting
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="on"/>
              <xs:enumeration value="off"/>
              <xs:enumeration value="2e"/>
              <xs:enumeration value="2f"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute default="off" name="xzero">
      <xs:annotation>
        <xs:documentation>
          Show x-zero (y=0) axis
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="off"/>
              <xs:enumeration value="line"/>
              <xs:enumeration value="thick-line"/>
              <xs:enumeration value="dotted"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute default="off" name="yzero">
      <xs:annotation>
        <xs:documentation>
          Show y-zero (x=0) axis
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="off"/>
              <xs:enumeration value="line"/>
              <xs:enumeration value="thick-line"/>
              <xs:enumeration value="dotted"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType name="lonplot--curve">
    <xs:annotation>
      <xs:documentation>Plot Curve</xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded" minOccurs="0">
      <xs:element name="data" type="lonplot--data"/>
      <xs:element name="function" type="lonplot--function"/>
    </xs:choice>
    <xs:attribute default="x000000" name="color" type="color-or-perl">
      <xs:annotation>
        <xs:documentation>
          Color of curve
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="name" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          Name of curve to appear in key
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="lines" name="linestyle">
      <xs:annotation>
        <xs:documentation>
          Unless otherwise noted the linestyles require only 2 data sets, X and Y.
          
          - lines: Connect adjacent points with straight line segments.
          - points: Display a small marker at each point.
          - linespoints: Draw both lines and points.
          Draws a small symbol at each point and then connects adjacent points with straight line segments.
          - dots: Place a tiny dots on the given points.
          - steps: Connect points with horizontal lines.
          This style connects consecutive points with two line segments: the first from (x1,y1) to (x2,y1) and the second from (x2,y1) to (x2,y2).
          - fsteps: Connect data with horizontal lines.
          This style connects consecutive points with two line segments: the first from (x1,y1) to (x1,y2) and the second from (x1,y2) to (x2,y2).
          - histeps: Plot as histogram.
          Y-values are assumed to be centered at the x-values; the point at x1 is represented as a horizontal line from ((x0+x1)/2,y1) to ((x1+x2)/2,y1). The lines representing the end points are extended so that the step is centered on at x. Adjacent points are connected by a vertical line at their average x, that is, from ((x1+x2)/2,y1) to ((x1+x2)/2,y2).
          - errorbars: Same as yerrorbars.
          - xerrorbars: Draw horizontal error bars around the points.
          Requires 3 or 4 data sets. Either X, Y, Xdelta or X, Y, Xlower, Xupper. Xdelta is a change relative to the given X value. The Xlower and Xupper values are absolute grid coordinates of the upper and lower values to indicated with error bars.
          - yerrorbars: Draw vertical error bars around the points.
          Requires 3 or 4 data sets. Either X, Y, Ydelta or X, Y, Ylower, Yupper. Ydelta is a change relative to the given Y value. The Ylower and Yupper values are the grid coordinates of the upper and lower values to indicate with error bars.
          - xyerrorbars: Draw both vertical and horizontal error bars around the points.
          Requires 4 or 6 data sets. Either X, Y, Xdelta, Ydelta or X, Y, Xlower, Xupper, Ylower, Yupper. Xdelta and Ydelta are relative to the given coordinates. Xlower, Xupper, Ylower, and Yupper are the grid coordinates of the upper and lower values to indicate with the error bars.
          - boxes: Draw a box from the X-axis to the Y-value given.
          Requires either 2 or 3 data sets. Either X, Y or X, Y, Xwidth. In the first case the boxes will be drawn next to eachother. In the latter case Xwidth indicates the horizontal width of the box for the given coordinate.
          - vector: Draws a vector field based on the given data.
          Requires 4 data sets, X, Y, Xdelta, and Ydelta. The ‘vector‘ style draws a vector from (X,Y) to (X+Xdelta,Y+Ydelta). It also draws a small arrowhead at the end of the vector. May not be fully supported by gnuplot.
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="lines"/>
              <xs:enumeration value="linespoints"/>
              <xs:enumeration value="dots"/>
              <xs:enumeration value="points"/>
              <xs:enumeration value="steps"/>
              <xs:enumeration value="fsteps"/>
              <xs:enumeration value="histeps"/>
              <xs:enumeration value="errorbars"/>
              <xs:enumeration value="xerrorbars"/>
              <xs:enumeration value="yerrorbars"/>
              <xs:enumeration value="xyerrorbars"/>
              <xs:enumeration value="boxes"/>
              <xs:enumeration value="filledcurves"/>
              <xs:enumeration value="vector"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute default="1" name="linewidth">
      <xs:annotation>
        <xs:documentation>
          Line width (may not apply to all plot styles)
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:int">
              <xs:minInclusive value="1"/>
              <xs:maxInclusive value="10"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute default="solid" name="linetype">
      <xs:annotation>
        <xs:documentation>
          Line type (may not apply to all plot styles)
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="solid"/>
              <xs:enumeration value="dashed"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute default="1" name="pointsize" type="int-or-perl">
      <xs:annotation>
        <xs:documentation>
          Point size (may not apply to all plot styles)
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="1" name="pointtype">
      <xs:annotation>
        <xs:documentation>
          Point type (may not apply to all plot styles)
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:int">
              <xs:minInclusive value="0"/>
              <xs:maxInclusive value="6"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute default="closed" name="limit">
      <xs:annotation>
        <xs:documentation>
          Point to fill for filled curves
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="above"/>
              <xs:enumeration value="below"/>
              <xs:enumeration value="closed"/>
              <xs:enumeration value="x1"/>
              <xs:enumeration value="x2"/>
              <xs:enumeration value="y1"/>
              <xs:enumeration value="y2"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute default="head" name="arrowhead">
      <xs:annotation>
        <xs:documentation>
          For vector plots, controls where in the vector the arrow head(s) appear.
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="nohead"/>
              <xs:enumeration value="head"/>
              <xs:enumeration value="heads"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute default="filled" name="arrowstyle">
      <xs:annotation>
        <xs:documentation>
          For vector plots, controls the fill style of the arrow.
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="filled"/>
              <xs:enumeration value="empty"/>
              <xs:enumeration value="nofilled"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute default="0.02" name="arrowlength" type="real-or-perl">
      <xs:annotation>
        <xs:documentation>
          For vector plots, determines the distance between the vector line end and the tip of the arrow.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="10.0" name="arrowangle" type="real-or-perl">
      <xs:annotation>
        <xs:documentation>
          For vector plots, determines the angle the arrow branches make with the vector line.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="90.0" name="arrowbackangle" type="real-or-perl">
      <xs:annotation>
        <xs:documentation>
          For vector plots, determines the angle the arrow lines that return to the main line from the branches make with the arrow branches.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType mixed="true" name="lonplot--data">
    <xs:annotation>
      <xs:documentation>
        Curve data
        
        The data must be either a perl array, @X, or a comma separated list, such as “0.5,0.9,1.5,2.4” (without quotes). ’NaN’ is a valid value. Note the the ”Y” values are entered in a separate array.
      </xs:documentation>
    </xs:annotation>
  </xs:complexType>
  <xs:complexType mixed="true" name="lonplot--function">
    <xs:annotation>
      <xs:documentation>
        Used to specify the curve to be plotted as a formula, instead of numerical data.
        The function must be a mathematical expression. Use the independent variable “x” for cartesian plots and “t” for polar plots. Implicit multiplication is not accepted by Gnuplot.
      </xs:documentation>
    </xs:annotation>
  </xs:complexType>
  <xs:complexType name="lonplot--key">
    <xs:annotation>
      <xs:documentation>
        Causes a key to be drawn on the plot when it is generated. The key will contain an entry for each curve which has a name.
        The key is the color of the foreground of the plot, specified in the gnuplot tag.
      </xs:documentation>
    </xs:annotation>
    <xs:attribute name="title" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          Title of key
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="off" name="box" type="onoff-or-perl">
      <xs:annotation>
        <xs:documentation>
          Draw a box around the key?
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="top right" name="pos">
      <xs:annotation>
        <xs:documentation>
          Position of the key on the plot
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="top left"/>
              <xs:enumeration value="top right"/>
              <xs:enumeration value="bottom left"/>
              <xs:enumeration value="bottom right"/>
              <xs:enumeration value="outside"/>
              <xs:enumeration value="below"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType mixed="true" name="lonplot--label">
    <xs:annotation>
      <xs:documentation>Plot Label</xs:documentation>
    </xs:annotation>
    <xs:attribute default="0" name="xpos" type="real-or-perl">
      <xs:annotation>
        <xs:documentation>
          X position of label (graph coordinates)
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="0" name="ypos" type="real-or-perl">
      <xs:annotation>
        <xs:documentation>
          Y position of label (graph coordinates)
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="left" name="justify">
      <xs:annotation>
        <xs:documentation>
          justification of the label text on the plot
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="left"/>
              <xs:enumeration value="right"/>
              <xs:enumeration value="center"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute default="0" name="rotate" type="real-or-perl">
      <xs:annotation>
        <xs:documentation>
          Rotation of label (degrees)
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType abstract="true" name="lonplot--tics">
    <xs:annotation>
      <xs:documentation>Plot tics</xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded" minOccurs="0">
      <xs:element name="tic">
        <xs:annotation>
          <xs:documentation>
            The <tic> tag allows users to specify exact Tic positions and labels for each axis.
            In this version we only support level 0 tics (major tic).
            Each tic has associated with it a position and a label $current_tics is a reference to the current tick description hash.
            We add elements to an array in that has: ticspecs whose elements are 'pos' - the tick position and 'label' - the tic label.
          </xs:documentation>
        </xs:annotation>
        <xs:complexType mixed="true">
          <xs:attribute name="location" type="real-or-perl" use="required"/>
        </xs:complexType>
      </xs:element>
    </xs:choice>
    <xs:attribute default="border" name="location">
      <xs:annotation>
        <xs:documentation>
          Location of major tic marks
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="border"/>
              <xs:enumeration value="axis"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute default="on" name="mirror" type="onoff-or-perl">
      <xs:annotation>
        <xs:documentation>
          Mirror tics on opposite axis?
          If the location of tic marks is set to “border” this parameter determines if they are shown on both the top and bottom or right and left sides of the graph. The “mirror” tic marks are unlabelled.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="-10.0" name="start" type="real-or-perl">
      <xs:annotation>
        <xs:documentation>
          The point in graph coordinates which to start making major tics. This may be less than or greater than the lower limit for the axis.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="1.0" name="increment" type="real-or-perl">
      <xs:annotation>
        <xs:documentation>
          The span, in graph coordinates, between each major tic mark.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="10.0" name="end" type="real-or-perl">
      <xs:annotation>
        <xs:documentation>
          Stop major tics at.
          This may be less than or greater than the upper limit for the axis.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="0" name="minorfreq" type="int-or-perl">
      <xs:annotation>
        <xs:documentation>
          The number of subdivisions to make of the span between major tic marks. Using a value of “10” leads to 9 minor tic marks.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="off" name="rotate" type="onoff-or-perl">
      <xs:annotation>
        <xs:documentation>
          For output devices that support it, this rotates the tic label by 90 degrees. This is most useful with large lables defined by the tic tag described below.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType name="lonplot--xtics">
    <xs:annotation>
      <xs:documentation>Plot xtics</xs:documentation>
    </xs:annotation>
    <xs:complexContent>
      <xs:extension base="lonplot--tics"/>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="lonplot--ytics">
    <xs:annotation>
      <xs:documentation>Plot ytics</xs:documentation>
    </xs:annotation>
    <xs:complexContent>
      <xs:extension base="lonplot--tics"/>
    </xs:complexContent>
  </xs:complexType>
  <xs:simpleType name="lonplot--xlabel">
    <xs:annotation>
      <xs:documentation>Plot x-label</xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string"/>
  </xs:simpleType>
  <xs:simpleType name="lonplot--ylabel">
    <xs:annotation>
      <xs:documentation>Plot y-label</xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string"/>
  </xs:simpleType>
  <xs:complexType name="lonplot--block">
    <xs:annotation>
      <xs:documentation>
        Conditional Block
        
        This has a required argument condition that is evaluated. If the condition is true, everything inside the tag is evaluated; otherwise, everything inside the block tag is skipped.
        
        When found inside the gnuplot element, a block can only have gnuplot children inside, with no text.
      </xs:documentation>
    </xs:annotation>
    <xs:choice maxOccurs="unbounded" minOccurs="0">
      <xs:group ref="gnuplot-children"/>
    </xs:choice>
    <xs:attribute name="condition" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          Test Condition
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>
  
  <xs:annotation>
    <xs:documentation>
      Task
    </xs:documentation>
  </xs:annotation>
  <xs:element name="Task">
    <xs:annotation>
      <xs:documentation>Root for .task (bridge task) documents</xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="IntroParagraph"/>
        <xs:element ref="Setup"/>
        <xs:element ref="Question"/>
        <xs:element ref="Criteria"/>
        <xs:element ref="ClosingParagraph"/>
      </xs:choice>
      <xs:attribute name="OptionalRequired" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            Required number of passed optional elements to pass the Task
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="IntroParagraph">
    <xs:annotation>
      <xs:documentation>Introductory Information</xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
        <xs:element ref="parserlib"/>
        <xs:element ref="scriptlib"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="ClosingParagraph">
    <xs:annotation>
      <xs:documentation>Closing Information</xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
        <xs:element ref="parserlib"/>
        <xs:element ref="scriptlib"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="Question">
    <xs:annotation>
      <xs:documentation>Question</xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="Instance"/>
        <xs:element ref="QuestionText"/>
        <xs:element ref="Question"/>
        <xs:element ref="Criteria"/>
      </xs:choice>
      <xs:attribute name="id" type="xs:string"/>
      <xs:attribute default="Y" name="Mandatory">
        <xs:annotation>
          <xs:documentation>
            Passing is Mandatory
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="Y"/>
                <xs:enumeration value="N"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="OptionalRequired" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            Required number of passed optional elements to pass
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="QuestionText">
    <xs:annotation>
      <xs:documentation>Question Information</xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
        <xs:element ref="parserlib"/>
        <xs:element ref="scriptlib"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="Setup">
    <xs:annotation>
      <xs:documentation>Setup....</xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="Instance"/>
      </xs:choice>
      <xs:attribute name="id" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="Instance">
    <xs:annotation>
      <xs:documentation>Specific Question Instance</xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="InstanceText"/>
        <xs:element ref="Criteria"/>
      </xs:choice>
      <xs:attribute name="id" type="xs:string"/>
      <xs:attribute default="no" name="Disabled" type="yesno-or-perl">
        <xs:annotation>
          <xs:documentation>
            Instance is Disabled
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="OptionalRequired" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            Required number of passed optional elements to pass the Instance
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="InstanceText">
    <xs:annotation>
      <xs:documentation>Information for the Instance</xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
        <xs:element ref="parserlib"/>
        <xs:element ref="scriptlib"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="Criteria">
    <xs:annotation>
      <xs:documentation>Question Criteria</xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="GraderNote"/>
        <xs:element ref="CriteriaText"/>
      </xs:choice>
      <xs:attribute name="id" type="xs:string"/>
      <xs:attribute default="Y" name="Mandatory">
        <xs:annotation>
          <xs:documentation>
            Passing is Mandatory
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="Y"/>
                <xs:enumeration value="N"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="CriteriaText">
    <xs:annotation>
      <xs:documentation>Criteria Information</xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
        <xs:element ref="parserlib"/>
        <xs:element ref="scriptlib"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="GraderNote">
    <xs:annotation>
      <xs:documentation>Text to display to Grader</xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
        <xs:element ref="parserlib"/>
        <xs:element ref="scriptlib"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  
  <xs:annotation>
    <xs:documentation>
      Problem block elements that cannot be used anywhere text is used.
    </xs:documentation>
  </xs:annotation>
  <xs:element name="part">
    <xs:annotation>
      <xs:documentation>
        Problem Part
        
        This must be below problem if it is going to be used. It does many of the same tasks as problem, but allows multiple separate problems to exist in a single file.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-with-responses"/>
        <xs:group ref="inserts"/>
        <xs:element ref="parameter"/>
        <xs:element ref="parserlib"/>
        <xs:element ref="scriptlib"/>
      </xs:choice>
      <xs:attribute name="id" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Part ID
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="display" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Displayed Part Description
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="allow">
    <xs:annotation>
      <xs:documentation>File Dependencies</xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="src" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Path to the file
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="parserlib" type="xs:anyURI">
    <xs:annotation>
      <xs:documentation>
        Import Tag Definitions
        
        The enclosed filename contains definitions for new tags.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="scriptlib" type="xs:anyURI">
    <xs:annotation>
      <xs:documentation>
        Import Script Library
        
        The enclosed filename contains Perl code to run in the safe space.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="meta">
    <xs:annotation>
      <xs:documentation>
        Custom Metadata for LON-CAPA (as opposed to the HTML meta which should be inside <head>).
        
        Recognized names:
        abstract, author, authorspace, avetries, avetries_list, clear, comefrom, comefrom_list, copyright, correct, count, course, course_list, courserestricted, creationdate, dependencies, depth, difficulty, difficulty_list, disc, disc_list, domain, end, field, firstname, generation, goto, goto_list, groupname, helpful, highestgradelevel, hostname, id, keynum, keywords, language, lastname, lastrevisiondate, lowestgradelevel, middlename, mime, modifyinguser, notes, owner, permanentemail, scope, sequsage, sequsage_list, standards, start, stdno, stdno_list, subject, technical, title, url, username, value, version.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="name" type="xs:string"/>
      <xs:attribute name="content" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="parameter">
    <xs:annotation>
      <xs:documentation>
        Parameter for a part
        
        parameter is exactly the same as responseparam, but should appear outside of a response tag.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="id" type="xs:string"/>
      <xs:attribute name="name" type="xs:string"/>
      <xs:attribute name="type" type="xs:string" use="required"/>
      <xs:attribute name="description" type="xs:string"/>
      <xs:attribute name="default" type="xs:string"/>
      <xs:attribute name="display" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Title displayed on the parameter setting screen.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="displaytitle">
    <xs:annotation>
      <xs:documentation>
        This will insert the title of the problem from the metadata of the problem. Only the first displaytitle in a problem will show the title; this allows clean usage of displaytitle in LON-CAPA style files.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="style" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="displayduedate">
    <xs:annotation>
      <xs:documentation>
        This will insert the current due date if one is set in the document.
        It is generated to be inside a table of 1x1 elements.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="style" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            style=“plain” Makes the due date appear without any boxing. If the parameter value is other than “plain”, or if the style parameter is omitted, the due date will be displayed within a box.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="format" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Allows you to control the format of the due date. This is an arbitrary string that can contain any of the following formatting items:
            
            %a Replaced by the abbreviated weekday name according to the current locale.
            %A Replaced by the full weekday name according to the current locale.
            %b The abbreviated month name according to the current locale.
            %B The full month name according to the current locale.
            %c The preferred date and time representation for the current locale (the default format string is just this).
            %C The century number as a two digit integer
            %d The day of the month as a decimal number. Leading zeroes are shown for single digit day numbers.
            %D Equivalent to %m/%d/%y
            %e Like %d but a leadnig zero is replaced by a space.
            %F Equivalent to %Y-%m-%d
            %G The four digit year number.
            %g The two digit year numbger.
            %H The hour as a two digit number in the range 00 thorugh 23.
            %I The hour as a two digit number in the range 00 through 12.
            %j The day your the year in the range 001 through 366.
            %k The hour (24 hour clock), single digits are preceded by a blank.
            %l Like %k but using a 12 hour clock.
            %m The month as a two digit decimal number in the range 01 through 12.
            %M The minute as a two digit decimal number in the range 00 through 59.
            %n A newline character.
            %p AM or PM depending on the time value.
            %P am or pm.
            %r The time in am or pm notation.
            %R Time in 24 hour notatinon (%H:%M). See also %T below.
            %s Number of seconds since midnight of January 1, 1970.
            %S The second as a decimal number int the range 00 through 59.
            %t A horizontal tab character.
            %T The time in 24 hour notation (%H:%M:%S).
            %u Day of the week as a decimal number with Monday as 1.
            %U The week number of the current year in the range 00 through 53. Week 1 is the week containing the first Sunday of the year.
            %V Same as %U but week 1 is the first week with at least 4 days, with Monday being the first day of a week.
            %w Day of the week as a decimal integer in the range 0 through 7, Sunday is 0.
            %W Week number of the current year in the range 00 through 53, where the first Monday of the year is the first day of week 01.
            %x The preferred date notation in the current locale without the time.
            %X The preferred time notation in the current locale without the date.
            %y The year as a decimal number without the century (range 00 through 99).
            %Y The year as a decimal number including the century.
            %% A % character.
            %+ Date and time in the form returned by the Unix date command. 
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="preduedate">
    <xs:annotation>
      <xs:documentation>
        Before Due Date Block
        
        Everything inside is skipped if the problem is after the due date.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-with-responses"/>
        <xs:element ref="displayduedate"/>
        <xs:element ref="displaytitle"/>
        <xs:element ref="parserlib"/>
        <xs:element ref="scriptlib"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="postanswerdate">
    <xs:annotation>
      <xs:documentation>
        After Answer Date Block
        
        Everything inside is skipped if the problem is before the answer date.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
        <xs:element ref="displayduedate"/>
        <xs:element ref="displaytitle"/>
        <xs:element ref="parserlib"/>
        <xs:element ref="scriptlib"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="solved">
    <xs:annotation>
      <xs:documentation>
        Block For After Solved
        
        Everything inside is skipped if the problem part is “not solved”.
        
        Should not be used outside of parts in a problem using parts.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
        <xs:group ref="inserts"/>
        <xs:element ref="parserlib"/>
        <xs:element ref="scriptlib"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="notsolved">
    <xs:annotation>
      <xs:documentation>
        Block For When Not Solved
        
        Everything inside is skipped if the problem part is “solved”.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-with-responses"/>
        <xs:group ref="inserts"/>
        <xs:element ref="parserlib"/>
        <xs:element ref="scriptlib"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  
  <xs:annotation>
    <xs:documentation>
      Non-HTML block elements mixed with text
    </xs:documentation>
  </xs:annotation>
  <xs:element name="import">
    <xs:annotation>
      <xs:documentation>
        Import a File
        
        This causes the parse to read in the file named in the body of the tag and parse it as if the entire text of the file had existed at the location of the tag.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:simpleContent>
        <xs:extension base="xs:anyURI">
          <xs:attribute name="id" type="xs:string"/>
          <xs:attribute name="importmode">
            <xs:annotation>
              <xs:documentation>
                Import as
              </xs:documentation>
            </xs:annotation>
            <xs:simpleType>
              <xs:union memberTypes="perl">
                <xs:simpleType>
                  <xs:restriction base="xs:string">
                    <xs:enumeration value="problem"/>
                    <xs:enumeration value="part"/>
                  </xs:restriction>
                </xs:simpleType>
              </xs:union>
            </xs:simpleType>
          </xs:attribute>
        </xs:extension>
      </xs:simpleContent>
    </xs:complexType>
  </xs:element>
  <xs:complexType mixed="true" name="block-base">
    <xs:annotation>
      <xs:documentation>
        Conditional Block
        
        This has a required argument condition that is evaluated. If the condition is true, everything inside the tag is evaluated; otherwise, everything inside the block tag is skipped.
      </xs:documentation>
    </xs:annotation>
    <xs:attribute name="condition" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          Test Condition
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType mixed="true" name="block-with-parts">
    <xs:complexContent mixed="true">
      <xs:extension base="block-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-with-parts"/>
          <xs:group ref="inserts"/>
          <xs:element ref="parserlib"/>
          <xs:element ref="scriptlib"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType mixed="true" name="block-with-responses">
    <xs:complexContent mixed="true">
      <xs:extension base="block-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-with-responses"/>
          <xs:group ref="inserts"/>
          <xs:element ref="parserlib"/>
          <xs:element ref="scriptlib"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType mixed="true" name="block-with-text">
    <xs:complexContent mixed="true">
      <xs:extension base="block-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-only"/>
          <xs:group ref="inserts"/>
          <xs:element ref="parserlib"/>
          <xs:element ref="scriptlib"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:annotation>
    <xs:documentation>
      see also: lonplot--block
    </xs:documentation>
  </xs:annotation>
  <xs:element name="while">
    <xs:annotation>
      <xs:documentation>
        While Loop Block
        
        This implements a while loop. The required attribute condition is a Perl scriptlet that when evaluated results in a true or false value. If true, the entirety of the text between the whiles is parsed. The condition is tested again, etc. If false, it goes to the next tag.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
        <xs:group ref="inserts"/>
        <xs:element ref="parserlib"/>
        <xs:element ref="scriptlib"/>
      </xs:choice>
      <xs:attribute name="condition" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Test Condition
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="tex" type="xs:string">
    <xs:annotation>
      <xs:documentation>
        Print Only Block (using only LaTeX)
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="print">
    <xs:annotation>
      <xs:documentation>
        Print Only Block (using HTML)
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="web">
    <xs:annotation>
      <xs:documentation>
        Web Only Block
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="standalone">
    <xs:annotation>
      <xs:documentation>
        Everything in between the start and end tag is shown only on the web and only if the resource is not part of a course.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:complexType mixed="true" name="problemtype-base">
    <xs:annotation>
      <xs:documentation>
        Problem Type Block
        
        Allows you to show or hide output based on what the problem-type parameter is set to in the course.
        Will only show the output text when the problem is set to the type of exam or survey in the course.
      </xs:documentation>
    </xs:annotation>
    <xs:attribute default="show" name="mode">
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="show"/>
              <xs:enumeration value="hide"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute default="exam" name="for">
      <xs:annotation>
        <xs:documentation>
          When used as type(s)
          
          Comma-separated list of values among:
          exam, survey, surveycred, anonsurvey, anonsurveycred, problem, practice, randomizetry
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:pattern value="(exam|survey|surveycred|anonsurvey|anonsurveycred|problem|practice|randomizetry)(\s*,\s*(exam|survey|surveycred|anonsurvey|anonsurveycred|problem|practice|randomizetry))*"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType mixed="true" name="problemtype-with-parts">
    <xs:complexContent mixed="true">
      <xs:extension base="problemtype-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-with-parts"/>
          <xs:group ref="inserts"/>
          <xs:element ref="parserlib"/>
          <xs:element ref="scriptlib"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType mixed="true" name="problemtype-with-responses">
    <xs:complexContent mixed="true">
      <xs:extension base="problemtype-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-with-responses"/>
          <xs:group ref="inserts"/>
          <xs:element ref="parserlib"/>
          <xs:element ref="scriptlib"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType mixed="true" name="problemtype-with-text">
    <xs:complexContent mixed="true">
      <xs:extension base="problemtype-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-only"/>
          <xs:group ref="inserts"/>
          <xs:element ref="parserlib"/>
          <xs:element ref="scriptlib"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="randomlist-base">
    <xs:annotation>
      <xs:documentation>
        Randomly Parsed Block
        
        The enclosed tags are parsed in a stable random order. The optional attribute show=“N” restricts the number of tags inside that are actually parsed to no more than N. N can equal the total tags inside. The randomlist tag can be used to randomize problem parts by wrapping the <part> tags with a randomlist tag. Note that when randomlist wraps <part> tags, that all students will work all parts only if show=“N” where N is the total number of parts wrapped. When N is less than the total number of parts wrapped, there will be gaps in the assessment chart, and also in the table of submissions for each student, corresponding to those parts which are never available to that particular student.
      </xs:documentation>
    </xs:annotation>
    <xs:attribute name="show" type="int-or-perl">
      <xs:annotation>
        <xs:documentation>
          Maximum Tags to Show
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType name="randomlist-with-parts">
    <xs:complexContent>
      <xs:extension base="randomlist-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:element ref="part"/>
          <xs:group ref="responses"/>
          <xs:element ref="img"/>
          <xs:element ref="postanswerdate"/>
          <xs:element ref="preduedate"/>
          <xs:element name="block" type="block-with-parts"/>
          <xs:element ref="while"/>
          <xs:element name="problemtype" type="problemtype-with-parts"/>
          <xs:element ref="window"/>
          <xs:element ref="display"/>
          <xs:element ref="gnuplot"/>
          <xs:element ref="organicstructure"/>
          <xs:element ref="instructorcomment"/>
          <xs:element ref="drawimage"/>
          <xs:element ref="import"/>
          
          <xs:element name="section" type="section-with-parts"/>
          <xs:element name="ul" type="ul-with-parts"/>
          <xs:element name="ol" type="ol-with-parts"/>
          <xs:element name="table" type="table-with-parts"/>
          <xs:element name="dl" type="dl-with-parts"/>
          <xs:element ref="object"/>
          <xs:element ref="applet"/>
          <xs:element ref="embed"/>
          <xs:element ref="video"/>
          <xs:element ref="audio"/>
          <xs:element ref="canvas"/>
          <xs:element ref="form"/>
          <xs:element ref="iframe"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="randomlist-with-responses">
    <xs:complexContent>
      <xs:extension base="randomlist-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="responses"/>
          <xs:element ref="img"/>
          <xs:element ref="postanswerdate"/>
          <xs:element ref="preduedate"/>
          <xs:element name="block" type="block-with-responses"/>
          <xs:element ref="while"/>
          <xs:element name="problemtype" type="problemtype-with-responses"/>
          <xs:element ref="window"/>
          <xs:element ref="display"/>
          <xs:element ref="gnuplot"/>
          <xs:element ref="organicstructure"/>
          <xs:element ref="instructorcomment"/>
          <xs:element ref="drawimage"/>
          <xs:element ref="import"/>
          
          <xs:element name="section" type="section-with-responses"/>
          <xs:element name="ul" type="ul-with-responses"/>
          <xs:element name="ol" type="ol-with-responses"/>
          <xs:element name="table" type="table-with-responses"/>
          <xs:element name="dl" type="dl-with-responses"/>
          <xs:element ref="object"/>
          <xs:element ref="applet"/>
          <xs:element ref="embed"/>
          <xs:element ref="video"/>
          <xs:element ref="audio"/>
          <xs:element ref="canvas"/>
          <xs:element ref="form"/>
          <xs:element ref="iframe"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="randomlist-with-text">
    <xs:complexContent>
      <xs:extension base="randomlist-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:element ref="img"/>
          <xs:element ref="postanswerdate"/>
          <xs:element ref="preduedate"/>
          <xs:element name="block" type="block-with-text"/>
          <xs:element ref="while"/>
          <xs:element name="problemtype" type="problemtype-with-text"/>
          <xs:element ref="window"/>
          <xs:element ref="display"/>
          <xs:element ref="gnuplot"/>
          <xs:element ref="organicstructure"/>
          <xs:element ref="instructorcomment"/>
          <xs:element ref="drawimage"/>
          <xs:element ref="import"/>
          
          <xs:element name="section" type="section-with-text"/>
          <xs:element name="ul" type="ul-with-text"/>
          <xs:element name="ol" type="ol-with-text"/>
          <xs:element name="table" type="table-with-text"/>
          <xs:element name="dl" type="dl-with-text"/>
          <xs:element ref="object"/>
          <xs:element ref="applet"/>
          <xs:element ref="embed"/>
          <xs:element ref="video"/>
          <xs:element ref="audio"/>
          <xs:element ref="canvas"/>
          <xs:element ref="form"/>
          <xs:element ref="iframe"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:element name="script">
    <xs:annotation>
      <xs:documentation>
        Perl Script Block
        
        If the attribute type is set to “loncapa/perl” the enclosed data is a Perl script which is evaluated inside the Perl safe space. The return value of the script is ignored.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:attribute default="text/javascript" name="type" type="xs:string"/>
      <xs:attribute fixed="preserve" ref="xml:space"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="languageblock">
    <xs:annotation>
      <xs:documentation>
        This declares the intent to provide content that can be rendered in the set of languages in the include specification but not in the exclude specification. If a currently preferred language is in the include list the content in the <languageblock>...</languageblock> is rendered If the currently preferred language is in the exclude list, the content in the <languageblock>..</languageblock is not rendered.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attribute name="include" type="xs:string"/>
      <xs:attribute name="exclude" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="translated">
    <xs:annotation>
      <xs:documentation>
        <translated> starts a block of a resource that has multiple translations.
        See the <lang> tag as well.
        When </translated> is encountered if there is a translation for the currently preferred language, that is rendered inthe web/tex/webgrade targets. Otherwise, the default text is rendered.
        Note that <lang> is only registered for the duration of the <translated>...</translated> block.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
        <xs:element ref="lang"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="lang">
    <xs:annotation>
      <xs:documentation>
        Specifies that the block contained within it is a translation for a specific language specified by the 'which' attribute. The 'other' attribute can be used by itself or in conjunction with which to specify this tag _may_ be used as a translation for some list of languages. e.g.:
        <lang which='senisoUS' other='senisoCA,senisoAU,seniso'>
        specifying that the block provides a translation for US (primary) Canadian, Australian and UK English.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attribute name="which" type="xs:string"/>
      <xs:attribute name="other" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="window">
    <xs:annotation>
      <xs:documentation>
        Text In Separate Window
        
        This creates a link that when clicked shows the intervening information in a pop-up window. By default the window will be 500 pixels wide and 200 pixels tall, and the link text will be a superscript * (so as to look like a footnote). These can be changed using the attributes.
        When printing, the included text will get turned into a real footnote.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
        <xs:group ref="inserts"/>
      </xs:choice>
      <xs:attribute name="linktext" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Text of Link
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="200" name="height" type="int-or-perl"/>
      <xs:attribute default="500" name="width" type="int-or-perl"/>
      <xs:attribute name="printtext" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Printed text (optional)
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="windowlink">
    <xs:annotation>
      <xs:documentation>
        This creates a link to a resource that comes up in a pop-up window.
        The link will be the intervening information between the start and the end tag.
        By default the window will be 500 pixels wide and 200 pixels tall.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attribute name="href" type="xs:anyURI"/>
      <xs:attribute name="height" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            starting height of the popup window
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="width" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            starting width of the popup window
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="togglebox">
    <xs:annotation>
      <xs:documentation>
        This creates a toggling box that can be clicked open and close.
        When printing, the included text will be rendered in a visible box.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attribute name="heading" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            heading text of the box, by default no heading
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="headerbg" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            background color of the header, by default white
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="showtext" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            the text that appears to make the box visible, by default the translation of ’show’
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="hidetext" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            the text that appears to hide the box again, by default the translation of ’hide’
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="instructorcomment">
    <xs:annotation>
      <xs:documentation>
        Comment that is hidden if form.instructor_comments='hide'.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="comment">
    <xs:annotation>
      <xs:documentation>
        Allows one to comment out sections of code in a balanced manner, or to provide a comment description of how a problem works.
        The content is ignored.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="part"/>
        <xs:group ref="text-with-responses"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="organicstructure">
    <xs:annotation>
      <xs:documentation>Organic Structure</xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="width" type="int-or-perl">
        <xs:annotation>
          <xs:documentation>
            Width (pixels)
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="texwidth" type="decimal-or-perl">
        <xs:annotation>
          <xs:documentation>
            TeXwidth (mm)
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="molecule" type="xs:string" use="required">
        <xs:annotation>
          <xs:documentation>
            JME string
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="reaction" name="options">
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="reaction"/>
                <xs:enumeration value="border"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="drawimage">
    <xs:annotation>
      <xs:documentation>
        Draws an image with the specified objects using pixel coordinates (text, line, rectangle, arc, fill, polygon, image).
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element name="text">
          <xs:complexType mixed="true">
            <xs:attribute name="x" type="int-or-perl" use="required"/>
            <xs:attribute name="y" type="int-or-perl" use="required"/>
            <xs:attribute name="font" type="xs:string"/>
            <xs:attribute name="color" type="xs:string"/>
            <xs:attribute name="direction" type="xs:string"/>
          </xs:complexType>
        </xs:element>
        <xs:element name="line">
          <xs:complexType>
            <xs:attribute name="x1" type="int-or-perl" use="required"/>
            <xs:attribute name="y1" type="int-or-perl" use="required"/>
            <xs:attribute name="x2" type="int-or-perl" use="required"/>
            <xs:attribute name="y2" type="int-or-perl" use="required"/>
            <xs:attribute name="color" type="xs:string"/>
            <xs:attribute name="thickness" type="int-or-perl"/>
          </xs:complexType>
        </xs:element>
        <xs:element name="rectangle">
          <xs:complexType>
            <xs:attribute name="x1" type="int-or-perl" use="required"/>
            <xs:attribute name="y1" type="int-or-perl" use="required"/>
            <xs:attribute name="x2" type="int-or-perl" use="required"/>
            <xs:attribute name="y2" type="int-or-perl" use="required"/>
            <xs:attribute name="color" type="xs:string"/>
            <xs:attribute name="thickness" type="int-or-perl"/>
            <xs:attribute name="filled" type="xs:string"/>
          </xs:complexType>
        </xs:element>
        <xs:element name="arc">
          <xs:complexType>
            <xs:attribute name="x" type="int-or-perl" use="required"/>
            <xs:attribute name="y" type="int-or-perl" use="required"/>
            <xs:attribute name="width" type="int-or-perl" use="required"/>
            <xs:attribute name="height" type="int-or-perl" use="required"/>
            <xs:attribute name="start" type="real-or-perl"/>
            <xs:attribute name="end" type="real-or-perl"/>
            <xs:attribute name="color" type="xs:string"/>
            <xs:attribute name="thickness" type="int-or-perl"/>
            <xs:attribute name="filled" type="xs:string"/>
          </xs:complexType>
        </xs:element>
        <xs:element name="fill">
          <xs:complexType>
            <xs:attribute name="x" type="int-or-perl" use="required"/>
            <xs:attribute name="y" type="int-or-perl" use="required"/>
            <xs:attribute name="color" type="xs:string"/>
          </xs:complexType>
        </xs:element>
        <xs:element name="polygon">
          <xs:complexType>
            <xs:choice maxOccurs="unbounded">
              <xs:element name="point">
                <xs:complexType>
                  <xs:attribute name="x" type="int-or-perl" use="required"/>
                  <xs:attribute name="y" type="int-or-perl" use="required"/>
                </xs:complexType>
              </xs:element>
            </xs:choice>
            <xs:attribute name="color" type="xs:string"/>
            <xs:attribute name="filled" type="xs:string"/>
            <xs:attribute name="open" type="xs:string"/>
            <xs:attribute name="thickness" type="int-or-perl"/>
          </xs:complexType>
        </xs:element>
        <xs:element name="image">
          <xs:complexType>
            <xs:simpleContent>
              <xs:extension base="xs:anyURI">
                <xs:attribute name="x" type="int-or-perl" use="required"/>
                <xs:attribute name="y" type="int-or-perl" use="required"/>
                <xs:attribute name="clipx" type="int-or-perl"/>
                <xs:attribute name="clipy" type="int-or-perl"/>
                <xs:attribute name="clipwidth" type="int-or-perl"/>
                <xs:attribute name="clipheight" type="int-or-perl"/>
                <xs:attribute name="scaledwidth" type="int-or-perl"/>
                <xs:attribute name="scaledheight" type="int-or-perl"/>
                <xs:attribute name="transparent" type="xs:string"/>
              </xs:extension>
            </xs:simpleContent>
          </xs:complexType>
        </xs:element>
      </xs:choice>
      <xs:attribute default="300" name="width" type="int-or-perl"/>
      <xs:attribute default="300" name="height" type="int-or-perl"/>
      <xs:attribute name="bgcolor" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  
  <xs:annotation>
    <xs:documentation>
      Non-HTML inline elements mixed with text
    </xs:documentation>
  </xs:annotation>
  <xs:element name="display" type="xs:string">
    <xs:annotation>
      <xs:documentation>
        Display Script Result Block
        
        The intervening Perl script is evaluated in the safe space and the return value of the script replaces the entire tag.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="m">
    <xs:annotation>
      <xs:documentation>
        The inside text is LaTeX, and is converted to HTML (or MathML) on the fly.
        This element is normally used for math, and the text should start and end with either $ or $$.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:attribute name="display" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Option to force the math rendering for this element.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="off" name="eval" type="onoff-or-perl">
        <xs:annotation>
          <xs:documentation>
            Perl variables inside the element will be evaluated if this attribute value is "on".
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="lm">
    <xs:annotation>
      <xs:documentation>
        Inline math with the LON-CAPA syntax (use <m> for LaTeX math).
        Perl variables are evaluated.
        The expression is interpreted with implicit operators (for multiplication or units).
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:simpleContent>
        <xs:extension base="xs:string">
          <xs:attribute default="symbols" name="mode">
            <xs:annotation>
              <xs:documentation>
                In symbols mode, names are interpreted as constants or variables.
                In units mode, names are interpreted as constants or units.
              </xs:documentation>
            </xs:annotation>
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="symbols"/>
                <xs:enumeration value="units"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:attribute>
        </xs:extension>
      </xs:simpleContent>
    </xs:complexType>
  </xs:element>
  <xs:element name="num">
    <xs:annotation>
      <xs:documentation>
        Typesets a number formatted in scientific notation, fixed point, fixed point with commas, fixed point with commas and dollar sign, or in significant digits.
        
        <num format="2E">31454678</num> results in 3.15 x 10^7
        <num format="2f">31454678</num> results in 31454678.00
        <num format="4g">31454678</num> results in 3.145 x 10^7
        <num format="4g">314.54678</num> results in 314.5
        <num format=",2f">31454678</num> results in 31,454,678.00
        <num format="$2f">31454678</num> results in $31,454,678.00
        <num format="2s">31454678</num> results in 31000000
        <num format=",2s">31454678</num> results in 31,000,000
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:attribute name="format" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="algebra">
    <xs:annotation>
      <xs:documentation>
        Typesets algebraic expressions.
        Expressions are displayed using the math expression display mechanism defined in the user’s preferences. The default is tth.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:attribute name="style" type="xs:string"/>
      <xs:attribute name="display" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="chem" type="xs:string">
    <xs:annotation>
      <xs:documentation>
        Typesets chemical equation
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="parse" type="xs:string">
    <xs:annotation>
      <xs:documentation>
        Evaluates the Perl content, then parses it as if it was part of the XML document and displays the result.
        
        Warning: using this element (or the xmlparse function) will reduce the document future interoperability, because dynamically generated XML cannot be automatically converted.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="displayweight">
    <xs:annotation>
      <xs:documentation>
        Displays the number of points awarded for this problem or problem part.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="displaystudentphoto">
    <xs:complexType>
      <xs:attribute name="width" type="int-or-perl"/>
      <xs:attribute name="height" type="int-or-perl"/>
      <xs:attribute name="align">
        <xs:annotation>
          <xs:documentation>
            note: this attribute is not supported in HTML5, css should be used instead !
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="left"/>
                <xs:enumeration value="right"/>
                <xs:enumeration value="middle"/>
                <xs:enumeration value="top"/>
                <xs:enumeration value="bottom"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  
  
  <xs:annotation>
    <xs:documentation>
      HTML
    </xs:documentation>
  </xs:annotation>
  <xs:attributeGroup name="coreattrs">
    <xs:annotation>
      <xs:documentation>
        core attributes common to most HTML elements
      </xs:documentation>
    </xs:annotation>
    <xs:attribute name="id" type="xs:ID">
      <xs:annotation>
        <xs:documentation>
          This attribute defines a unique identifier (ID) which must be unique in the whole document. Its purpose is to identify the element when linking (using a fragment identifier), scripting, or styling (with CSS).
          
          Usage note:
          - This attribute's value is an opaque string: this means that web author must not use it to convey any information. Particular meaning, for example semantic meaning, must not be derived from the string.
          - This attribute's value must not contain white spaces. Browsers treat non-conforming IDs that contains white spaces as if the white space is part of the ID. In contrast to the class attribute, which allows space-separated values, elements can only have one single ID defined through the id attribute. Note that an element may have several IDs, but the others should be set by another means, such as via a script interfacing with the DOM interface of the element.
          - Using characters except ASCII letters and digits, '_', '-' and '.' may cause compatibility problems, as they weren't allowed in HTML 4. Though this restriction has been lifted in HTML 5, an ID should start with a letter for compatibility.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="class" type="xs:NMTOKENS">
      <xs:annotation>
        <xs:documentation>
          This attribute is a space-separated list of the classes of the element. Classes allows CSS and Javascript to select and access specific elements via the class selectors or functions like the DOM method document.getElementsByClassName.
          
          Usage note: Though the specification doesn't put requirements on the name of classes, web developers are encouraged to use names that describe the semantic purpose of the element, rather to the presentation of the element (e.g., attribute to describe an attribute rather than italics, although an element of this class may be presented by italics). Semantic names remain logical even if the presentation of the page changes.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="style" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          This attribute contains CSS styling declarations to be applied to the element. Note that it is recommended for styles to be defined in a separate file or files. This attribute and the <style> element have mainly the purpose of allowing for quick styling, for example for testing purposes.
          
          Usage note: This attribute must not be used to convey semantic information. Even if all styling is removed, a page should remain semantically correct. Typically it shouldn't be used to hide irrelevant information; this should be done using the hidden attribute.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:attributeGroup>
  <xs:attributeGroup name="i18n">
    <xs:annotation>
      <xs:documentation>
        internationalization attributes
        lang language code (backwards compatible)
        xml:lang language code (as per XML 1.0 spec)
        dir direction for weak/neutral text
      </xs:documentation>
    </xs:annotation>
    <xs:attribute name="lang" type="xs:language"/>
    <xs:attribute ref="xml:lang"/>
    <xs:attribute name="dir">
      <xs:simpleType>
        <xs:restriction base="xs:token">
          <xs:enumeration value="ltr"/>
          <xs:enumeration value="rtl"/>
        </xs:restriction>
      </xs:simpleType>
    </xs:attribute>
  </xs:attributeGroup>
  <xs:complexType mixed="true" name="inlineBaseType">
    <xs:choice maxOccurs="unbounded" minOccurs="0">
      <xs:group ref="inlines"/>
    </xs:choice>
  </xs:complexType>
  <xs:group name="heading">
    <xs:choice>
      <xs:element ref="h1"/>
      <xs:element ref="h2"/>
      <xs:element ref="h3"/>
      <xs:element ref="h4"/>
      <xs:element ref="h5"/>
      <xs:element ref="h6"/>
    </xs:choice>
  </xs:group>
  <xs:complexType mixed="true" name="headerContent">
    <xs:choice maxOccurs="unbounded" minOccurs="0">
      <xs:group ref="inlines"/>
    </xs:choice>
    <xs:attributeGroup ref="coreattrs"/>
    <xs:attribute ref="TeXsize"/>
  </xs:complexType>
  
  <xs:element name="html">
    <xs:annotation>
      <xs:documentation>
        The HTML root element (<html>) represents the root of an HTML document.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="head"/>
        <xs:element ref="body"/>
      </xs:sequence>
      <xs:attributeGroup ref="i18n"/>
      <xs:attribute name="id" type="xs:ID"/>
    </xs:complexType>
  </xs:element>
  <xs:group name="head.misc">
    <xs:sequence>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element name="script" type="htmlScript"/>
        <xs:element ref="style"/>
        <xs:element name="meta" type="htmlMeta">
          <xs:annotation>
            <xs:documentation>
              generic metainformation
            </xs:documentation>
          </xs:annotation>
        </xs:element>
        <xs:element ref="link"/>
        <xs:element ref="import"/>
      </xs:choice>
    </xs:sequence>
  </xs:group>
  <xs:element name="head">
    <xs:annotation>
      <xs:documentation>
        The HTML Head Element (<head>) provides general information (metadata) about the document, including its title and links to or definitions of scripts and style sheets.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:sequence>
        <xs:group ref="head.misc"/>
        <xs:choice>
          <xs:sequence>
            <xs:element ref="title"/>
            <xs:group ref="head.misc"/>
            <xs:sequence minOccurs="0">
              <xs:element ref="base"/>
              <xs:group ref="head.misc"/>
            </xs:sequence>
          </xs:sequence>
          <xs:sequence>
            <xs:element ref="base"/>
            <xs:group ref="head.misc"/>
            <xs:element ref="title"/>
            <xs:group ref="head.misc"/>
          </xs:sequence>
        </xs:choice>
      </xs:sequence>
      <xs:attributeGroup ref="i18n"/>
      <xs:attribute name="id" type="xs:ID"/>
      <xs:attribute name="profile" type="xs:anyURI">
        <xs:annotation>
          <xs:documentation>
            The URIs of one or more metadata profiles, separated by white space.
            
            This attribute is obsolete in HTML5.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="title">
    <xs:annotation>
      <xs:documentation>
        The title element is not considered part of the flow of text.
        It should be displayed, for example as the page header or
        window title. Exactly one title is required per document.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:attributeGroup ref="i18n"/>
      <xs:attribute name="id" type="xs:ID"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="base">
    <xs:annotation>
      <xs:documentation>
        Document base URI
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="href" type="xs:anyURI" use="required"/>
      <xs:attribute name="id" type="xs:ID"/>
    </xs:complexType>
  </xs:element>
  <xs:complexType name="htmlMeta">
    <xs:attributeGroup ref="i18n"/>
    <xs:attribute name="id" type="xs:ID"/>
    <xs:attribute name="http-equiv">
      <xs:annotation>
        <xs:documentation>
          This enumerated attribute defines the pragma that can alter servers and user-agents behavior. The value of the pragma is defined using the content and can be one of the following: 
          - content-language (obsolete)
          - content-type (obsolete)
          - default-style
          - refresh
          - set-cookie (obsolete)
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="name">
      <xs:annotation>
        <xs:documentation>
          This attribute defines the name of a document-level metadata. It should not be set if one of the attributes itemprop, http-equiv or charset is also set.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="content" use="required">
      <xs:annotation>
        <xs:documentation>
          This attribute gives the value associated with the http-equiv or name attribute, depending of the context.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="scheme">
      <xs:annotation>
        <xs:documentation>
          This attribute defines the scheme in which the metadata is described. A scheme is a context leading to the correct interpretations of the content value, like a format.
          
          Notes: Do not use this attribute as it is obsolete. There is no replacement for it as there was no real usage for it. Omit it altogether.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>
  <xs:simpleType name="MediaDesc">
    <xs:annotation>
      <xs:documentation>
        Single or comma-separated list of media descriptors
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string">
      <xs:pattern value="[^,]+(,\s*[^,]+)*"/>
    </xs:restriction>
  </xs:simpleType>
  <xs:element name="link">
    <xs:annotation>
      <xs:documentation>
        The HTML Link Element (<link>) specifies relationships between the current document and external resource. Possible uses for this element include defining a relational framework for navigation. This Element is most used to link to style sheets.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="charset" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            This attribute defines the character encoding of the linked resource. The value is a space- and/or comma-delimited list of character sets as defined in RFC 2045. The default value is ISO-8859-1.
            
            Usage note: This attribute is obsolete in HTML5 and must not be used by authors. To achieve its effect, use the Content-Type: HTTP header on the linked resource.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="href" type="xs:anyURI">
        <xs:annotation>
          <xs:documentation>
            This attribute specifies the URL of the linked resource. A URL might be absolute or relative.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="hreflang" type="xs:language">
        <xs:annotation>
          <xs:documentation>
            This attribute indicates the language of the linked resource. It is purely advisory. Allowed values are determined by BCP47 for HTML5 and by RFC1766 for HTML 4. Use this attribute only if the href attribute is present.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="type" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            This attribute is used to define the type of the content linked to. The value of the attribute should be a MIME type such as text/html, text/css, and so on. The common use of this attribute is to define the type of style sheet linked and the most common current value is text/css, which indicates a Cascading Style Sheet format.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="rel" type="xs:NMTOKENS">
        <xs:annotation>
          <xs:documentation>
            This attribute names a relationship of the linked document to the current document. The attribute must be a space-separated list of the link types values. The most common use of this attribute is to specify a link to an external style sheet: the rel attribute is set to stylesheet, and the href attribute is set to the URL of an external style sheet to format the page. WebTV also supports the use of the value next for rel to preload the next page in a document series.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="rev" type="xs:NMTOKENS">
        <xs:annotation>
          <xs:documentation>
            The value of this attribute shows the relationship of the current document to the linked document, as defined by the href attribute. The attribute thus defines the reverse relationship compared to the value of the rel attribute. Link types values for the attribute are similar to the possible values for rel.
            
            Usage note: This attribute is obsolete in HTML5. Do not use it. To achieve its effect, use the rel attribute with the opposite link types values, e.g. made should be replaced by author. Also this attribute doesn't mean revision and must not be used with a version number, which is unfortunately the case on numerous sites.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="media" type="MediaDesc">
        <xs:annotation>
          <xs:documentation>
            This attribute specifies the media which the linked resource applies to. Its value must be a media query. This attribute is mainly useful when linking to external stylesheets by allowing the user agent to pick the best adapted one for the device it runs on.
            
            Usage note:
            
            - In HTML 4, this can only be a simple white-space-separated list of media description literals, i.e., media types and groups, where defined and allowed as values for this attribute, such as print, screen, aural, braille. HTML5 extended this to any kind of media queries, which are a superset of the allowed values of HTML 4.
            
            - Browsers not supporting the CSS3 Media Queries won't necessarily recognize the adequate link; do not forget to set fallback links, the restricted set of media queries defined in HTML 4.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="style">
    <xs:annotation>
      <xs:documentation>
        The HTML <style> element contains style information for a document, or a part of document. The specific style information is contained inside of this element, usually in the CSS.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:attributeGroup ref="i18n"/>
      <xs:attribute name="id" type="xs:ID"/>
      <xs:attribute default="text/css" name="type" type="xs:string"/>
      <xs:attribute name="media" type="MediaDesc"/>
      <xs:attribute name="title" type="xs:string"/>
      <xs:attribute fixed="preserve" ref="xml:space"/>
    </xs:complexType>
  </xs:element>
  <xs:complexType mixed="true" name="htmlScript">
    <xs:annotation>
      <xs:documentation>
        The HTML <script> element is used to embed or reference an executable script within an HTML or XHTML document.
        
        Scripts without async or defer attributes, as well as inline scripts, are fetched and executed immediately, before the browser continues to parse the page.
      </xs:documentation>
    </xs:annotation>
    <xs:attribute name="id" type="xs:ID"/>
    <xs:attribute name="charset" type="xs:string"/>
    <xs:attribute name="src" type="xs:anyURI">
      <xs:annotation>
        <xs:documentation>
          This attribute specifies the URI of an external script; this can be used as an alternative to embedding a script directly within a document. script elements with an src attribute specified should not have a script embedded within its tags.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute default="text/javascript" name="type" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          This attribute identifies the scripting language of code embedded within a script element or referenced via the element’s src attribute. This is specified as a MIME type; examples of supported MIME types include text/javascript, text/ecmascript, application/javascript, and application/ecmascript.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="language" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          Like the type attribute, this attribute identifies the scripting language in use. Unlike the type attribute, however, this attribute’s possible values were never standardized. The type attribute should be used instead.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="defer">
      <xs:annotation>
        <xs:documentation>
          This Boolean attribute is set to indicate to a browser that the script is meant to be executed after the document has been parsed. Since this feature hasn't yet been implemented by all other major browsers, authors should not assume that the script’s execution will actually be deferred. The defer attribute shouldn't be used on scripts that don't have the src attribute. Since Gecko 1.9.2, the defer attribute is ignored on scripts that don't have the src attribute. However, in Gecko 1.9.1 even inline scripts are deferred if the defer attribute is set.
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:restriction base="xs:token">
          <xs:enumeration value="defer"/>
        </xs:restriction>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute name="async">
      <xs:annotation>
        <xs:documentation>
          HTML5 only.
          Set this Boolean attribute to indicate that the browser should, if possible, execute the script asynchronously. It has no effect on inline scripts (i.e., scripts that don't have the src attribute).
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:restriction base="xs:token">
          <xs:enumeration value="async"/>
        </xs:restriction>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute fixed="preserve" ref="xml:space"/>
  </xs:complexType>
  <xs:element name="noscript">
    <xs:annotation>
      <xs:documentation>
        Alternate content container for non script-based rendering.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="blocks-with-text"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="body">
    <xs:annotation>
      <xs:documentation>
        The HTML <body> element represents the content of an HTML document. There is only one <body> element in a document.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="blocks-with-text"/>
      </xs:choice>
      <xs:attributeGroup ref="coreattrs"/>
      <xs:attributeGroup ref="i18n"/>
      <xs:attribute name="onload" type="xs:string"/>
      <xs:attribute name="onunload" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:complexType mixed="true" name="section-base">
    <xs:annotation>
      <xs:documentation>
        The HTML Section Element (<section>) represents a generic section of a document, i.e., a thematic grouping of content, typically with a heading. Each <section> should be identified, typically by including a heading (h1-h6 element) as a child of the <section> element.
      </xs:documentation>
    </xs:annotation>
    <xs:attributeGroup ref="coreattrs"/>
  </xs:complexType>
  <xs:complexType mixed="true" name="section-with-parts">
    <xs:complexContent mixed="true">
      <xs:extension base="section-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-with-parts"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType mixed="true" name="section-with-responses">
    <xs:complexContent mixed="true">
      <xs:extension base="section-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-with-responses"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType mixed="true" name="section-with-text">
    <xs:complexContent mixed="true">
      <xs:extension base="section-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-only"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:element name="header">
    <xs:annotation>
      <xs:documentation>
        The HTML <header> Element represents a group of introductory or navigational aids. It may contain some heading elements but also other elements like a logo, wrapped section's header, a search form, and so on.
        
        This element should have no <footer> or <header> descendants.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="footer">
    <xs:annotation>
      <xs:documentation>
        The HTML <footer> Element represents a footer for its nearest sectioning content or sectioning root element (i.e, its nearest parent <article>, <aside>, <nav>, <section>, <blockquote>, <body>, <details>, <fieldset>, <figure>, <td>). A footer typically contains information about the author of the section, copyright data or links to related documents.
        
        This element should have no <footer> or <header> descendants.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="aside">
    <xs:annotation>
      <xs:documentation>
        The HTML <aside> element represents a section of the page with content connected tangentially to the rest, which could be considered separate from that content. These sections are often represented as sidebars or inserts. They often contain the definitions on the sidebars, such as definitions from the glossary; there may also be other types of information, such as related advertisements; the biography of the author; web applications; profile information or related links on the blog.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="h1" type="headerContent">
    <xs:annotation>
      <xs:documentation>
        Level 1 title (most important).
        
        A heading element briefly describes the topic of the section it introduces. Heading information may be used by user agents, for example, to construct a table of contents for a document automatically.
        
        Inside HTML5 sections, all the heading elements can be h1 (they don't need to be h1, h2, ...). Web browsers determine the level of the heading based on the depth in the section tree.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="h2" type="headerContent">
    <xs:annotation>
      <xs:documentation>
        Level 2 title
        
        A heading element briefly describes the topic of the section it introduces. Heading information may be used by user agents, for example, to construct a table of contents for a document automatically.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="h3" type="headerContent">
    <xs:annotation>
      <xs:documentation>
        Level 3 title
        
        A heading element briefly describes the topic of the section it introduces. Heading information may be used by user agents, for example, to construct a table of contents for a document automatically.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="h4" type="headerContent">
    <xs:annotation>
      <xs:documentation>
        Level 4 title
        
        A heading element briefly describes the topic of the section it introduces. Heading information may be used by user agents, for example, to construct a table of contents for a document automatically.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="h5" type="headerContent">
    <xs:annotation>
      <xs:documentation>
        Level 5 title
        
        A heading element briefly describes the topic of the section it introduces. Heading information may be used by user agents, for example, to construct a table of contents for a document automatically.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="h6" type="headerContent">
    <xs:annotation>
      <xs:documentation>
        Level 6 title
        
        A heading element briefly describes the topic of the section it introduces. Heading information may be used by user agents, for example, to construct a table of contents for a document automatically.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:complexType mixed="true" name="div-base">
    <xs:annotation>
      <xs:documentation>
        The HTML <div> element (or HTML Document Division Element) is the generic container for flow content, which does not inherently represent anything. It can be used to group elements for styling purposes (using the class or id attributes), or because they share attribute values, such as lang. It should be used only when no other semantic element (such as <article> or <nav>) is appropriate.
      </xs:documentation>
    </xs:annotation>
    <xs:attributeGroup ref="coreattrs"/>
    <xs:attribute name="align">
      <xs:annotation>
        <xs:documentation>
          In HTML5, the align attribute on <div> is obsolete.
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:restriction base="xs:string">
          <xs:enumeration value="center"/>
          <xs:enumeration value="left"/>
          <xs:enumeration value="right"/>
        </xs:restriction>
      </xs:simpleType>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType mixed="true" name="div-with-parts">
    <xs:complexContent mixed="true">
      <xs:extension base="div-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-with-parts"/>
          <xs:group ref="inserts"/>
          <xs:element ref="allow"/>
          <xs:element ref="meta"/>
          <xs:element ref="parameter"/>
          <xs:element ref="parserlib"/>
          <xs:element ref="scriptlib"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType mixed="true" name="div-with-responses">
    <xs:complexContent mixed="true">
      <xs:extension base="div-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-with-responses"/>
          <xs:group ref="inserts"/>
          <xs:element ref="allow"/>
          <xs:element ref="meta"/>
          <xs:element ref="parameter"/>
          <xs:element ref="parserlib"/>
          <xs:element ref="scriptlib"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType mixed="true" name="div-with-text">
    <xs:complexContent mixed="true">
      <xs:extension base="div-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-only"/>
          <xs:group ref="inserts"/>
          <xs:element ref="meta"/>
          <xs:element ref="parameter"/>
          <xs:element ref="parserlib"/>
          <xs:element ref="scriptlib"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType mixed="true" name="p-base">
    <xs:annotation>
      <xs:documentation>
        The HTML <p> element (or HTML Paragraph Element) represents a paragraph of text. Paragraphs are block-level elements.
      </xs:documentation>
    </xs:annotation>
    <xs:attributeGroup ref="coreattrs"/>
  </xs:complexType>
  <xs:complexType mixed="true" name="p-with-responses">
    <xs:complexContent mixed="true">
      <xs:extension base="p-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="inlines"/>
          <xs:group ref="inlineResponses"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType mixed="true" name="p-with-text">
    <xs:complexContent mixed="true">
      <xs:extension base="p-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="inlines"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="ul-base">
    <xs:annotation>
      <xs:documentation>
        Unordered list
      </xs:documentation>
    </xs:annotation>
    <xs:attributeGroup ref="coreattrs"/>
    <xs:attribute name="type" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          Used to set the bullet style for the list. The values defined under HTML3.2 and the transitional version of HTML 4.0/4.01 are:
          - circle,
          - disc,
          - and square.
          
          A fourth bullet type has been defined in the WebTV interface, but not all browsers support it: triangle.
          
          If not present and if no CSS list-style-type property does apply to the element, the user agent decide to use a kind of bullets depending on the nesting level of the list.
          Usage note: Do not use this attribute, as it has been deprecated; use the CSS list-style-type property instead.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType name="ul-with-parts">
    <xs:complexContent>
      <xs:extension base="ul-base">
        <xs:sequence maxOccurs="unbounded">
          <xs:element name="li" type="li-with-parts"/>
        </xs:sequence>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="ul-with-responses">
    <xs:complexContent>
      <xs:extension base="ul-base">
        <xs:sequence maxOccurs="unbounded">
          <xs:element name="li" type="li-with-responses"/>
        </xs:sequence>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="ul-with-text">
    <xs:complexContent>
      <xs:extension base="ul-base">
        <xs:sequence maxOccurs="unbounded">
          <xs:element name="li" type="li-with-text"/>
        </xs:sequence>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="ol-base">
    <xs:annotation>
      <xs:documentation>
        Ordered list
      </xs:documentation>
    </xs:annotation>
    <xs:attributeGroup ref="coreattrs"/>
    <xs:attribute name="type" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          Indicates the numbering type:
          - 'a' indicates lowercase letters,
          - 'A' indicates uppercase letters,
          - 'i' indicates lowercase Roman numerals,
          - 'I' indicates uppercase Roman numerals,
          - and '1' indicates numbers (default).
          
          The type set is used for the entire list unless a different type attribute is used within an enclosed <li> element.
          
          Note: This attribute was deprecated in HTML4, but reintroduced in HTML5. Unless the value of the list number matters (e.g. in legal or technical documents where items are to be referenced by their number/letter), the CSS list-style-type property should be used instead.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType name="ol-with-parts">
    <xs:complexContent>
      <xs:extension base="ol-base">
        <xs:sequence maxOccurs="unbounded">
          <xs:element name="li" type="li-with-parts"/>
        </xs:sequence>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="ol-with-responses">
    <xs:complexContent>
      <xs:extension base="ol-base">
        <xs:sequence maxOccurs="unbounded">
          <xs:element name="li" type="li-with-responses"/>
        </xs:sequence>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="ol-with-text">
    <xs:complexContent>
      <xs:extension base="ol-base">
        <xs:sequence maxOccurs="unbounded">
          <xs:element name="li" type="li-with-text"/>
        </xs:sequence>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType mixed="true" name="li-base">
    <xs:annotation>
      <xs:documentation>
        List item
      </xs:documentation>
    </xs:annotation>
    <xs:attributeGroup ref="coreattrs"/>
    <xs:attribute name="type" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          This character attributes indicates the numbering type:
          
          a: lowercase letters
          A: uppercase letters
          i: lowercase Roman numerals
          I: uppercase Roman numerals
          1: numbers
          
          This type overrides the one used by its parent <ol> element, if any.
          
          Usage note: This attribute has been deprecated: use the CSS list-style-type property instead.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="value" type="xs:int">
      <xs:annotation>
        <xs:documentation>
          This integer attributes indicates the current ordinal value of the item in the list as defined by the <ol> element. The only allowed value for this attribute is a number, even if the list is displayed with Roman numerals or letters. List items that follow this one continue numbering from the value set. The value attribute has no meaning for unordered lists (<ul>) or for menus (<menu>).
          
          Note: This attribute was deprecated in HTML4, but reintroduced in HTML5.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType mixed="true" name="li-with-parts">
    <xs:complexContent mixed="true">
      <xs:extension base="li-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-with-parts"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType mixed="true" name="li-with-responses">
    <xs:complexContent mixed="true">
      <xs:extension base="li-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-with-responses"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType mixed="true" name="li-with-text">
    <xs:complexContent mixed="true">
      <xs:extension base="li-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-only"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="dl-base">
    <xs:annotation>
      <xs:documentation>
        The HTML <dl> Element (or HTML Description List Element) encloses a list of pairs of terms and descriptions. Common uses for this element are to implement a glossary or to display metadata (a list of key-value pairs).
        
        Prior to HTML5, <dl> was known as a Definition List.
      </xs:documentation>
    </xs:annotation>
    <xs:attributeGroup ref="coreattrs"/>
  </xs:complexType>
  <xs:complexType name="dl-with-parts">
    <xs:complexContent>
      <xs:extension base="dl-base">
        <xs:choice maxOccurs="unbounded">
          <xs:element ref="dt"/>
          <xs:element name="dd" type="dd-with-parts"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="dl-with-responses">
    <xs:complexContent>
      <xs:extension base="dl-base">
        <xs:choice maxOccurs="unbounded">
          <xs:element ref="dt"/>
          <xs:element name="dd" type="dd-with-responses"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="dl-with-text">
    <xs:complexContent>
      <xs:extension base="dl-base">
        <xs:choice maxOccurs="unbounded">
          <xs:element ref="dt"/>
          <xs:element name="dd" type="dd-with-text"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:element name="dt" type="inlineBaseType">
    <xs:annotation>
      <xs:documentation>
        The HTML <dt> element (or HTML Definition Term Element) identifies a term in a definition list. This element can occur only as a child element of a <dl>. It is usually followed by a <dd> element; however, multiple <dt> elements in a row indicate several terms that are all defined by the immediate next <dd> element.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:complexType mixed="true" name="dd-base">
    <xs:annotation>
      <xs:documentation>
        The HTML <dd> Element (or HTML Description Element) indicates the description of a term in a description list (<dl>) element. This element can occur only as a child element of a definition list and it must follow a <dt> element.
      </xs:documentation>
    </xs:annotation>
  </xs:complexType>
  <xs:complexType mixed="true" name="dd-with-parts">
    <xs:complexContent mixed="true">
      <xs:extension base="dd-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-with-parts"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType mixed="true" name="dd-with-responses">
    <xs:complexContent mixed="true">
      <xs:extension base="dd-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-with-responses"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType mixed="true" name="dd-with-text">
    <xs:complexContent mixed="true">
      <xs:extension base="dd-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-only"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="table-base">
    <xs:annotation>
      <xs:documentation>
        The HTML Table Element (<table>) represents data in two dimensions or more.
      </xs:documentation>
    </xs:annotation>
    <xs:attributeGroup ref="coreattrs"/>
    <xs:attribute name="border" type="xs:int">
      <xs:annotation>
        <xs:documentation>
          This integer attribute defines, in pixels, the size of the frame surrounding the table. If set to 0, it implies that the frame attribute is set to void.
          Usage note: Do not use this attribute, as it has been deprecated: the <table> element should be styled using CSS. To give a similar effect than the border attribute, the CSS properties border, border-color, border-width and border-style should be used.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="cellpadding" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          This attribute defines the space between the content of a cell and the border, displayed or not, of it. If it is a pixel length, this pixel-sized space will be applied on all four sides; if it is a percentage length, the content will be centered and the total vertical space (top and bottom) will represent this percentage. The same is true for the total horizontal space (left and right).
          Usage note: Do not use this attribute, as it has been deprecated: the <table> element should be styled using CSS. To give a similar effect than the border attribute, use the CSS property border-collapse with the value collapse on the <table> element itself, and the property padding on the <td>.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="cellspacing" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          This attribute defines the size, in percentage or in pixels, of the space between two cells (both horizontally and vertically), between the top of the table and the cells of the first row, the left of the table and the first column, the right of the table and the last column and the bottom of the table and the last row.
          Usage note: Do not use this attribute, as it has been deprecated: the <table> element should be styled using CSS. To give a similar effect than the border attribute, use the CSS property border-collapse with the value collapse on the <table> element itself, and the property margin on the <td> element.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="TeXwidth">
      <xs:annotation>
        <xs:documentation>
          Width of the table in %
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:pattern value="[0-9]+\s*%"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute name="TeXtheme" type="xs:string"/>
    <xs:attribute name="align" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          Deprecated attribute.
          
          This enumerated attribute indicates how the table must be aligned in regard of the containing document. It may have the following values:
          
          - left, meaning that the table is to be displayed to the left of the document;
          - center, meaning that the table is to be displayed centered in the document;
          - right, meaning that the table is to be displayed to the right of the document.
          
          Note: 
          Do not use this attribute, as it has been deprecated: the <table> element should be styled using CSS. To give a similar effect than the align attribute, the CSS properties "text-align" and "vertical-align" should be used.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="rules" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          Deprecated attribute.
          
          This enumerated attribute defines where rules, i.e. lines, should appear in a table. It can have the following values:
          
          - none, which indicates the no rules will be displayed; it is the default value;
          - groups, which will make the rules to be displayed between row groups (defined by the <thead>, <tbody> and <tfoot> elements) and between column groups (defined by the <col> and <colgroup> elements) only;
          - rows, which will make the rules to be displayed between rows;
          - columns, which will make the rules to be displayed between columns;
          - all, which wil make the rules to be displayed between rows and columns.
          
          Note:
          The styling of the rules is browser-dependant and cannot be modified.
          Do not use this attribute, as it has been deprecated: the rules should be defined and styled using CSS. use the CSS property border on the adequate <thead>, <tbody>, <tfoot>, <col> or <colgroup> elements.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType name="table-with-parts">
    <xs:complexContent>
      <xs:extension base="table-base">
        <xs:sequence>
          <xs:element minOccurs="0" ref="caption"/>
          <xs:element minOccurs="0" ref="thead"/>
          <xs:element minOccurs="0" ref="tfoot"/>
          <xs:choice>
            <xs:element maxOccurs="unbounded" name="tbody" type="tbody-with-parts"/>
            <xs:element maxOccurs="unbounded" name="tr" type="tr-with-parts"/>
          </xs:choice>
        </xs:sequence>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="table-with-responses">
    <xs:complexContent>
      <xs:extension base="table-base">
        <xs:sequence>
          <xs:element minOccurs="0" ref="caption"/>
          <xs:element minOccurs="0" ref="thead"/>
          <xs:element minOccurs="0" ref="tfoot"/>
          <xs:choice>
            <xs:element maxOccurs="unbounded" name="tbody" type="tbody-with-responses"/>
            <xs:element maxOccurs="unbounded" name="tr" type="tr-with-responses"/>
          </xs:choice>
        </xs:sequence>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="table-with-text">
    <xs:complexContent>
      <xs:extension base="table-base">
        <xs:sequence>
          <xs:element minOccurs="0" ref="caption"/>
          <xs:element minOccurs="0" ref="thead"/>
          <xs:element minOccurs="0" ref="tfoot"/>
          <xs:choice>
            <xs:element maxOccurs="unbounded" name="tbody" type="tbody-with-text"/>
            <xs:element maxOccurs="unbounded" name="tr" type="tr-with-text"/>
          </xs:choice>
        </xs:sequence>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:element name="caption">
    <xs:annotation>
      <xs:documentation>
        The HTML <caption> Element (or HTML Table Caption Element) represents the title of a table. Though it is always the first descendant of a <table>, its styling, using CSS, may place it elsewhere, relative to the table.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="thead">
    <xs:annotation>
      <xs:documentation>
        The HTML Table Head Element (<thead>) defines a set of rows defining the head of the columns of the table.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" name="tr" type="tr-with-text"/>
      </xs:sequence>
      <xs:attribute name="align" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Deprecated attribute.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="tfoot">
    <xs:annotation>
      <xs:documentation>
        The HTML Table Foot Element (<tfoot>) defines a set of rows summarizing the columns of the table.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" name="tr" type="tr-with-text"/>
      </xs:sequence>
      <xs:attribute name="align" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Deprecated attribute.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:complexType name="tbody-with-parts">
    <xs:sequence>
      <xs:choice>
        <xs:element maxOccurs="unbounded" name="tr" type="tr-with-parts"/>
      </xs:choice>
    </xs:sequence>
    <xs:attribute name="align" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          Deprecated attribute.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType name="tbody-with-responses">
    <xs:sequence>
      <xs:choice>
        <xs:element maxOccurs="unbounded" name="tr" type="tr-with-responses"/>
      </xs:choice>
    </xs:sequence>
    <xs:attribute name="align" type="xs:string"/>
  </xs:complexType>
  <xs:complexType name="tbody-with-text">
    <xs:sequence>
      <xs:choice>
        <xs:element maxOccurs="unbounded" name="tr" type="tr-with-text"/>
      </xs:choice>
    </xs:sequence>
    <xs:attribute name="align" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          Deprecated attribute.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType name="tr-base">
    <xs:annotation>
      <xs:documentation>
        Table row
      </xs:documentation>
    </xs:annotation>
    <xs:attributeGroup ref="coreattrs"/>
    <xs:attribute name="align" type="xs:string">
      <xs:annotation>
        <xs:documentation>
          Deprecated attribute.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType name="tr-with-parts">
    <xs:complexContent>
      <xs:extension base="tr-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:element ref="th"/>
          <xs:element name="td" type="td-with-parts"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="tr-with-responses">
    <xs:complexContent>
      <xs:extension base="tr-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:element ref="th"/>
          <xs:element name="td" type="td-with-responses"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="tr-with-text">
    <xs:complexContent>
      <xs:extension base="tr-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:element ref="th"/>
          <xs:element name="td" type="td-with-text"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:simpleType name="html-align">
    <xs:restriction base="xs:string">
      <xs:enumeration value="left"/>
      <xs:enumeration value="center"/>
      <xs:enumeration value="right"/>
      <xs:enumeration value="justify"/>
    </xs:restriction>
  </xs:simpleType>
  <xs:complexType mixed="true" name="td-base">
    <xs:annotation>
      <xs:documentation>
        Table cell
      </xs:documentation>
    </xs:annotation>
    <xs:attributeGroup ref="coreattrs"/>
    <xs:attribute name="colspan" type="xs:int">
      <xs:annotation>
        <xs:documentation>
          This attribute contains a non-negative integer value that indicates on how many columns does the cell extend. Its default value is 1.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="rowspan" type="xs:int">
      <xs:annotation>
        <xs:documentation>
          This attribute contains a non-negative integer value that indicates on how many rows does the cell extend. Its default value is 1.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="align" type="html-align"/>
    <xs:attribute name="TeXwidth">
      <xs:annotation>
        <xs:documentation>
          Width of the cell in mm or another unit (cm, in, pt, pc)
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:union memberTypes="perl">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:pattern value="\d*(\s+(mm|cm|in|pt|pc))?"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:union>
      </xs:simpleType>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType mixed="true" name="td-with-parts">
    <xs:complexContent mixed="true">
      <xs:extension base="td-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-with-parts"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType mixed="true" name="td-with-responses">
    <xs:complexContent mixed="true">
      <xs:extension base="td-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-with-responses"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType mixed="true" name="td-with-text">
    <xs:complexContent mixed="true">
      <xs:extension base="td-base">
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-only"/>
        </xs:choice>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:element name="th">
    <xs:annotation>
      <xs:documentation>
        Table header cell
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="coreattrs"/>
      <xs:attribute name="colspan" type="xs:int">
        <xs:annotation>
          <xs:documentation>
            This attribute contains a non-negative integer value that indicates on how many columns does the cell extend. Its default value is 1.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="rowspan" type="xs:int">
        <xs:annotation>
          <xs:documentation>
            This attribute contains a non-negative integer value that indicates on how many rows does the cell extend. Its default value is 1.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="align" type="html-align"/>
      <xs:attribute name="scope">
        <xs:annotation>
          <xs:documentation>
            defines the cells that the header defined in this <th> element relates to
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="row"/>
            <xs:enumeration value="col"/>
            <xs:enumeration value="rowgroup"/>
            <xs:enumeration value="colgroup"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="TeXwidth">
        <xs:annotation>
          <xs:documentation>
            Width of the cell in mm or another unit (cm, in, pt, pc)
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:pattern value="\d*(\s+(mm|cm|in|pt|pc))?"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="span">
    <xs:annotation>
      <xs:documentation>
        Inline style
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="inlines"/>
      </xs:choice>
      <xs:attributeGroup ref="coreattrs"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="a">
    <xs:annotation>
      <xs:documentation>
        The HTML <a> Element (or the HTML Anchor Element) defines a hyperlink, the named target destination for a hyperlink, or both.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="inlines"/>
      </xs:choice>
      <xs:attributeGroup ref="coreattrs"/>
      <xs:attribute name="name" type="xs:string"/>
      <xs:attribute name="href" type="xs:anyURI"/>
      <xs:attribute name="target" type="xs:string"/>
      <xs:attribute name="title" type="xs:string"/>
      <xs:attribute name="uriprint" type="xs:string"/>
      <xs:attribute name="anchorprint" type="xs:string"/>
      <xs:attribute name="rel" type="xs:NMTOKENS"/>
      <xs:attribute name="accesskey">
        <xs:simpleType>
          <xs:restriction base="xs:string">
            <xs:length fixed="true" value="1"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="onclick" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Javascript event handler content attribute for the "click" event.
            
            Warning: event handler content attributes should be avoided. They make the markup bigger and less readable. Concerns of content/structure and behavior are not well-separated, making a bug harder to find. Furthermore, usage of event attributes almost always causes scripts to expose global functions on the Window object, polluting the global namespace.
            
            The EventTarget.addEventListener() function should be used instead to add a listener for the event.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="em" type="inlineBaseType">
    <xs:annotation>
      <xs:documentation>
        Emphasis
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="strong" type="inlineBaseType">
    <xs:annotation>
      <xs:documentation>
        Strong emphasis
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="b" type="inlineBaseType">
    <xs:annotation>
      <xs:documentation>
        Bold
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="i" type="inlineBaseType">
    <xs:annotation>
      <xs:documentation>
        Italic
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="sup" type="inlineBaseType">
    <xs:annotation>
      <xs:documentation>
        Superscript
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="sub" type="inlineBaseType">
    <xs:annotation>
      <xs:documentation>
        Subscript
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="pre" type="inlineBaseType">
    <xs:annotation>
      <xs:documentation>
        The HTML <pre> Element (or HTML Preformatted Text) represents preformatted text. Text within this element is typically displayed in a non-proportional font exactly as it is laid out in the file. Whitespaces inside this element are displayed as typed.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="code" type="inlineBaseType">
    <xs:annotation>
      <xs:documentation>
        The HTML <code> Element represents a fragment of computer code. By default, it is displayed in the browser's default monospace font.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="kbd" type="inlineBaseType">
    <xs:annotation>
      <xs:documentation>
        The HTML <kbd> Element (or HTML Keyboard Input Element) represents user input and produces an inline element displayed in the browser's default monotype font.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="samp" type="inlineBaseType">
    <xs:annotation>
      <xs:documentation>
        The HTML <samp> element is an element intended to identify sample output from a computer program. It is usually displayed in the browser's default monotype font.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="cite" type="inlineBaseType">
    <xs:annotation>
      <xs:documentation>
        The HTML <cite> Element (or HTML Citation Element) represents a reference to a creative work. It must include the title of a work, the name of the author, or a URL reference, which may be in an abbreviated form according to the conventions used for the addition of citation metadata.
        
        Usage Notes:
        
        A creative work may include a book, a paper, an essay, a poem, a score, a song, a script, a film, a TV show, a game, a sculpture, a painting, a theater production, a play, an opera, a musical, an exhibition, a legal case report, a computer program, , a web site, a web page, a blog post or comment, a forum post or comment, a tweet, a written or oral statement, etc.
        Use the cite attribute on a <blockquote> or <q> element to reference an online resource for a source.
        
        
        Style note:
        
        To avoid the default italic style from being used for the <cite> element use the CSS font-style property.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="q">
    <xs:annotation>
      <xs:documentation>
        The HTML <q> Element (or HTML Quote Element) indicates that the enclosed text is a short inline quotation. This element is intended for short quotations that don't require paragraph breaks; for long quotations use <blockquote> element.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="inlines"/>
      </xs:choice>
      <xs:attribute name="cite" type="xs:anyURI">
        <xs:annotation>
          <xs:documentation>
            The value of this attribute is a URL that designates a source document or message for the information quoted. This attribute is intended to point to information explaining the context or the reference for the quote.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="tt" type="inlineBaseType">
    <xs:annotation>
      <xs:documentation>
        This feature is obsolete. Although it may still work in some browsers, its use is discouraged since it could be removed at any time. Try to avoid using it.
        
        The HTML Teletype Text Element (<tt>) produces an inline element displayed in the browser's default monotype font. This element was intended to style text as it would display on a fixed width display, such as a teletype. It probably is more common to display fixed width type using the <code> element.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="ins">
    <xs:annotation>
      <xs:documentation>
        The HTML <ins> Element (or HTML Inserted Text) HTML represents a range of text that has been added to a document.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="inlineBaseType">
          <xs:attribute name="cite" type="xs:anyURI"/>
          <xs:attribute name="datetime" type="xs:dateTime"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>
  <xs:element name="del">
    <xs:annotation>
      <xs:documentation>
        The HTML <del> element (or HTML Deleted Text Element) represents a range of text that has been deleted from a document. This element is often (but need not be) rendered with strike-through text.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="inlineBaseType">
          <xs:attribute name="cite" type="xs:anyURI"/>
          <xs:attribute name="datetime" type="xs:dateTime"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>
  <xs:element name="var" type="inlineBaseType">
    <xs:annotation>
      <xs:documentation>
        The HTML Variable Element (<var>) represents a variable in a mathematical expression or a programming context.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="small" type="inlineBaseType">
    <xs:annotation>
      <xs:documentation>
        The HTML Small Element (<small>) makes the text font size one size smaller (for example, from large to medium, or from small to x-small) down to the browser's minimum font size. In HTML5, this element is repurposed to represent side-comments and small print, including copyright and legal text, independent of its styled presentation.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="big" type="inlineBaseType">
    <xs:annotation>
      <xs:documentation>
        This feature is obsolete. Although it may still work in some browsers, its use is discouraged since it could be removed at any time. Try to avoid using it.
      </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="br">
    <xs:annotation>
      <xs:documentation>
        The HTML <br> Element (or HTML Line Break Element) produces a line break in text (carriage-return). It is useful for writing a poem or an address, where the division of lines is significant.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
    </xs:complexType>
  </xs:element>
  <xs:element name="hr">
    <xs:annotation>
      <xs:documentation>
        The HTML <hr> element represents a thematic break between paragraph-level elements (for example, a change of scene in a story, or a shift of topic with a section). In previous versions of HTML, it represented a horizontal rule. It may still be displayed as a horizontal rule in visual browsers, but is now defined in semantic terms, rather than presentational terms.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attributeGroup ref="coreattrs"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="address">
    <xs:annotation>
      <xs:documentation>
        The HTML <address> Element may be used by authors to supply contact information for its nearest <article> or <body> ancestor; in the latter case, it applies to the whole document.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="coreattrs"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="blockquote">
    <xs:annotation>
      <xs:documentation>
        The HTML <blockquote> Element (or HTML Block Quotation Element) indicates that the enclosed text is an extended quotation. Usually, this is rendered visually by indentation (to change <blockquote> indent, use CSS margin property). A URL for the source of the quotation may be given using the cite attribute, while a text representation of the source can be given using the <cite> element.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="coreattrs"/>
      <xs:attribute name="cite" type="xs:anyURI"/>
      <xs:attribute name="align">
        <xs:simpleType>
          <xs:restriction base="xs:string">
            <xs:enumeration value="center"/>
            <xs:enumeration value="left"/>
            <xs:enumeration value="right"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:simpleType name="htmlLength">
    <xs:annotation>
      <xs:documentation>
        nn for pixels or nn% for percentage length
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string">
      <xs:pattern value="[\-+]?(\d+|\d+(\.\d+)?%)"/>
    </xs:restriction>
  </xs:simpleType>
  <xs:simpleType name="htmlLength-or-perl">
    <xs:union memberTypes="htmlLength perl"/>
  </xs:simpleType>
  <xs:element name="img">
    <xs:annotation>
      <xs:documentation>
        The HTML <img> Element (or HTML Image Element) represents an image of the document.
        
        Usage note:
        Browsers do not always display the image referenced by the element. This is the case for non-graphical browsers (including those used by people with vision impairments), or if the user chooses not to display images, or if the browser is unable to display the image because it is invalid or an unsupported type. In these cases, the browser may replace the image with the text defined in this element's alt attribute.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attributeGroup ref="coreattrs"/>
      <xs:attribute name="src" type="xs:anyURI" use="required">
        <xs:annotation>
          <xs:documentation>
            Image URL.
            On browsers supporting srcset, src is ignored if this one is provided.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="alt" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            This attribute defines the alternative text describing the image. Users will see this displayed if the image URL is wrong, the image is not in one of the supported formats, or until the image is downloaded.
            
            Usage note: Omitting this attribute indicates that the image is a key part of the content, but no textual equivalent is available. Setting this attribute to the empty string indicates that this image is not a key part of the content; non-visual browsers may omit it from rendering.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="width" type="htmlLength-or-perl">
        <xs:annotation>
          <xs:documentation>
            The width of the image in pixels or percent.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="height" type="htmlLength-or-perl">
        <xs:annotation>
          <xs:documentation>
            The height of the image in pixels or percent.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="TeXwidth">
        <xs:annotation>
          <xs:documentation>
            Allows you to set the width of the image, in mm or %, as it will be rendered into the LaTeX document used to print the problem.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:pattern value="[0-9]+(\.[0-9]+)?(\s*%)?"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="TeXheight" type="decimal-or-perl">
        <xs:annotation>
          <xs:documentation>
            Allows you to set the height of the image, in mm, as it will be rendered into the LaTeX document used to print the problem.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="align">
        <xs:annotation>
          <xs:documentation>
            This attribute is deprecated since HTML 4.01 and obsolete since HTML5. Use the vertical-align CSS property instead.
            
            Specifies the alignment of the image relative to the enclosing text paragraph:
            - bottom: The image will be aligned so that its bottom will be at the baseline of the surrounding text.
            - middle: The image will be aligned so that its center-line will be at the baseline of the surrounding text.
            - top: The image will be aligned so that its top will be at the baseline of the surrounding text.
            - left: The image will be placed so that it is at the left of the surrounding text. The surrounding text will fill in the region to the right of the image.
            - right: The image will be placed so that it is at the right of the surrounding text. The surrounding text will fill in the region to the left of the image.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="bottom"/>
                <xs:enumeration value="middle"/>
                <xs:enumeration value="top"/>
                <xs:enumeration value="left"/>
                <xs:enumeration value="right"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="TeXwrap">
        <xs:annotation>
          <xs:documentation>
            Allows you to select how the LaTeX document will attempt to wrap text around a horizontally aligned image.
            parbox: \newline and \parbox will be used to place the image. This method ensures that text will not be wrapped on top of the image, however very little text will appear next to the image itself.
            parpic: The picins package \parpic command will be used to place the image. This will wrap the remainder of the paragraph containing the picture around the image.
            If, however, there is insufficient text to fill the space to the left or right of the image, the next paragraph may be wrapped on top of the image. In addition, \parpic does not always honor the end of the page, causing the image to extend below the page footer.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:union memberTypes="perl">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="none"/>
                <xs:enumeration value="parbox"/>
                <xs:enumeration value="parpic"/>
                <xs:enumeration value="wrapfigure"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:union>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute default="no" name="encrypturl" type="yesno-or-perl"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="figure">
    <xs:annotation>
      <xs:documentation>
        The HTML <figure> Element represents self-contained content, frequently with a caption (<figcaption>), and is typically referenced as a single unit. While it is related to the main flow, its position is independent of the main flow. Usually this is an image, an illustration, a diagram, a code snippet, or a schema that is referenced in the main text, but that can be moved to another page or to an appendix without affecting the main flow.
        
        Usage note: A caption can be associated with the <figure> element by inserting a <figcaption> inside it (as the first or the last child).
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice minOccurs="0">
        <xs:sequence>
          <xs:element ref="figcaption"/>
          <xs:choice maxOccurs="unbounded" minOccurs="0">
            <xs:group ref="text-only"/>
          </xs:choice>
        </xs:sequence>
        <xs:sequence>
          <xs:choice maxOccurs="unbounded">
            <xs:group ref="text-only"/>
          </xs:choice>
          <xs:element minOccurs="0" ref="figcaption"/>
        </xs:sequence>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="figcaption">
    <xs:annotation>
      <xs:documentation>
        The HTML <figcaption> Element represents a caption or a legend associated with a figure or an illustration described by the rest of the data of the <figure> element which is its immediate ancestor which means <figcaption> can be the first or last element inside a <figure> block. Also, the HTML Figcaption Element is optional; if not provided, then the parent figure element will have no caption.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
      </xs:choice>
    </xs:complexType>
  </xs:element>
  <xs:element name="object">
    <xs:annotation>
      <xs:documentation>
        The HTML <object> Element (or HTML Embedded Object Element) represents an external resource, which can be treated as an image, a nested browsing context, or a resource to be handled by a plugin.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="param"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attributeGroup ref="coreattrs"/>
      <xs:attribute name="classid" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            The URI of the object's implementation. It can be used together with, or in place of, the data attribute.
            Obsolete since HTML5.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="codebase" type="xs:anyURI">
        <xs:annotation>
          <xs:documentation>
            The base path used to resolve relative URIs specified by classid, data, or archive. If not specified, the default is the base URI of the current document.
            Obsolete since HTML5.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="data" type="xs:anyURI">
        <xs:annotation>
          <xs:documentation>
            The address of the resource as a valid URL. At least one of data and type must be defined.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="type" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            The content type of the resource specified by data. At least one of data and type must be defined.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="codetype" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            The content type of the data specified by classid.
            Obsolete since HTML5.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="archive">
        <xs:annotation>
          <xs:documentation>
            A space-separated list of URIs for archives of resources for the object.
            Obsolete since HTML5.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:list itemType="xs:anyURI"/>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="standby" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            A message that the browser can show while loading the object's implementation and data.
            Obsolete since HTML5.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="width" type="htmlLength-or-perl">
        <xs:annotation>
          <xs:documentation>
            The width of the display resource, in CSS pixels.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="height" type="htmlLength-or-perl">
        <xs:annotation>
          <xs:documentation>
            The height of the displayed resource, in CSS pixels.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="usemap" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            A hash-name reference to a <map> element; that is a '#' followed by the value of a name of a map element.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="name" type="xs:NMTOKEN">
        <xs:annotation>
          <xs:documentation>
            The name of valid browsing context (HTML5), or the name of the control (HTML 4).
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="param">
    <xs:annotation>
      <xs:documentation>
        param is used to supply a named property value
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="id" type="xs:ID"/>
      <xs:attribute name="name">
        <xs:annotation>
          <xs:documentation>
            Name of the parameter.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="value">
        <xs:annotation>
          <xs:documentation>
            Specifies the value of the parameter.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="data" name="valuetype">
        <xs:annotation>
          <xs:documentation>
            Obsolete in HTML5.
            
            Specifies the type of the value attribute.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="data"/>
            <xs:enumeration value="ref"/>
            <xs:enumeration value="object"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="type" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Obsolete in HTML5.
            
            Only used if the valuetype is set to "ref". Specifies the MIME type of values found at the URI specified by value.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="embed">
    <xs:annotation>
      <xs:documentation>
        The HTML <embed> Element represents an integration point for an external application or interactive content (in other words, a plug-in).
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="id" type="xs:ID"/>
      <xs:attribute name="src" type="xs:anyURI">
        <xs:annotation>
          <xs:documentation>
            The URL of the resource being embedded.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="type" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            The MIME type to use to select the plug-in to instantiate.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="width" type="non-negative-int-or-perl">
        <xs:annotation>
          <xs:documentation>
            The displayed width of the resource, in CSS pixels.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="height" type="non-negative-int-or-perl">
        <xs:annotation>
          <xs:documentation>
            The displayed height of the resource, in CSS pixels.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="applet">
    <xs:annotation>
      <xs:documentation>
        This feature is obsolete. Although it may still work in some browsers, its use is discouraged since it could be removed at any time. Try to avoid using it.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="param"/>
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attribute name="id" type="xs:ID"/>
      <xs:attribute name="codebase" type="xs:anyURI"/>
      <xs:attribute name="archive" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Comma-separated list of URIs for archives containing classes and other resources that will be "preloaded".
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="code"/>
      <xs:attribute name="object"/>
      <xs:attribute name="alt" type="xs:string"/>
      <xs:attribute name="name" type="xs:NMTOKEN"/>
      <xs:attribute name="width" type="non-negative-int-or-perl" use="required"/>
      <xs:attribute name="height" type="non-negative-int-or-perl" use="required"/>
      <xs:attribute name="align">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="top"/>
            <xs:enumeration value="middle"/>
            <xs:enumeration value="bottom"/>
            <xs:enumeration value="left"/>
            <xs:enumeration value="right"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="hspace" type="xs:nonNegativeInteger"/>
      <xs:attribute name="vspace" type="xs:nonNegativeInteger"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="video">
    <xs:annotation>
      <xs:documentation>
        The HTML <video> element is used to embed video content. It may contain several video sources, represented using the src attribute or the <source> element; the browser will choose the most suitable one.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="source"/>
      </xs:choice>
      <xs:attributeGroup ref="coreattrs"/>
      <xs:attribute name="src" type="xs:anyURI"/>
      <xs:attribute name="width" type="non-negative-int-or-perl"/>
      <xs:attribute name="height" type="non-negative-int-or-perl"/>
      <xs:attribute name="autoplay">
        <xs:simpleType>
          <xs:restriction base="xs:string">
            <xs:enumeration value="autoplay"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="controls">
        <xs:simpleType>
          <xs:restriction base="xs:string">
            <xs:enumeration value="controls"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="loop">
        <xs:simpleType>
          <xs:restriction base="xs:string">
            <xs:enumeration value="loop"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="source">
    <xs:annotation>
      <xs:documentation>
        The HTML <source> element is used to specify multiple media resources for <picture>, <audio> and <video> elements. It is an empty element. It is commonly used to serve the same media in multiple formats supported by different browsers.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="src" type="xs:anyURI" use="required"/>
      <xs:attribute name="type" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="audio">
    <xs:annotation>
      <xs:documentation>
        The HTML <audio> element is used to embed sound content in documents. It may contain several audio sources, represented using the src attribute or the <source> element; the browser will choose the most suitable one.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="source"/>
      </xs:choice>
      <xs:attributeGroup ref="coreattrs"/>
      <xs:attribute name="src" type="xs:anyURI"/>
      <xs:attribute name="autoplay">
        <xs:simpleType>
          <xs:restriction base="xs:string">
            <xs:enumeration value="autoplay"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="controls">
        <xs:simpleType>
          <xs:restriction base="xs:string">
            <xs:enumeration value="controls"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="loop">
        <xs:simpleType>
          <xs:restriction base="xs:string">
            <xs:enumeration value="loop"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  
  <xs:element name="map">
    <xs:annotation>
      <xs:documentation>
        The HTML <map> element is used with <area> elements to define an image map (a clickable link area).
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice>
        <xs:choice maxOccurs="unbounded">
          <xs:group ref="blocks-with-text"/>
        </xs:choice>
        <xs:element maxOccurs="unbounded" ref="area"/>
      </xs:choice>
      <xs:attribute name="id" type="xs:ID" use="required"/>
      <xs:attribute name="class" type="xs:NMTOKENS"/>
      <xs:attribute name="style" type="xs:string"/>
      <xs:attribute name="title" type="xs:string"/>
      <xs:attribute name="name" type="xs:NMTOKEN"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="area">
    <xs:annotation>
      <xs:documentation>
        The HTML <area> element defines a hot-spot region on an image, and optionally associates it with a hypertext link. This element is used only within a <map> element.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute default="rect" name="shape">
        <xs:annotation>
          <xs:documentation>
            The shape of the associated hot spot. The specifications for HTML 5 and HTML 4 define the values rect, which defines a rectangular region; circle, which defines a circular region; poly, which defines a polygon; and default, which indicates the entire region beyond any defined shapes.
            Many browsers, notably Internet Explorer 4 and higher, support circ, polygon, and rectangle as valid values for shape; these values are *not standard*.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="rect"/>
            <xs:enumeration value="circle"/>
            <xs:enumeration value="poly"/>
            <xs:enumeration value="default"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="coords">
        <xs:annotation>
          <xs:documentation>
            A set of values specifying the coordinates of the hot-spot region. The number and meaning of the values depend upon the value specified for the shape attribute. For a rect or rectangle shape, the coords value is two x,y pairs: left, top, right, and bottom. For a circle shape, the value is x,y,r where x,y is a pair specifying the center of the circle and r is a value for the radius. For a poly or polygon< shape, the value is a set of x,y pairs for each point in the polygon: x1,y1,x2,y2,x3,y3, and so on. In HTML4, the values are numbers of pixels or percentages, if a percent sign (%) is appended; in HTML5, the values are numbers of CSS pixels.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:restriction base="xs:string">
            <xs:pattern value="[\-+]?(\d+|\d+(\.\d+)?%)(,\s*[\-+]?(\d+|\d+(\.\d+)?%))*"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="href" type="xs:anyURI">
        <xs:annotation>
          <xs:documentation>
            The hyperlink target for the area. Its value is a valid URL. In HTML4, either this attribute or the nohref attribute must be present in the element. In HTML5, this attribute may be omitted; if so, the area element does not represent a hyperlink.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="nohref">
        <xs:annotation>
          <xs:documentation>
            Indicates that no hyperlink exists for the associated area. Either this attribute or the href attribute must be present in the element.
            
            Usage note: This attribute is obsolete in HTML5, instead omitting the href attribute is sufficient.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="nohref"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="alt" type="xs:string" use="required">
        <xs:annotation>
          <xs:documentation>
            A text string alternative to display on browsers that do not display images. The text should be phrased so that it presents the user with the same kind of choice as the image would offer when displayed without the alternative text. In HTML4, this attribute is required, but may be the empty string (""). In HTML5, this attribute is required only if the href attribute is used.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="canvas">
    <xs:annotation>
      <xs:documentation>
        The HTML <canvas> Element can be used to draw graphics via scripting (usually JavaScript). For example, it can be used to draw graphs, make photo compositions or even perform animations. You may (and should) provide alternate content inside the <canvas> block. That content will be rendered both on older browsers that don't support canvas and in browsers with JavaScript disabled.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="blocks-with-text"/>
        <xs:group ref="inlines"/>
      </xs:choice>
      <xs:attributeGroup ref="coreattrs"/>
      <xs:attribute default="300" name="width" type="htmlLength-or-perl">
        <xs:annotation>
          <xs:documentation>
            The width of the coordinate space in CSS pixels.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="150" name="height" type="htmlLength-or-perl">
        <xs:annotation>
          <xs:documentation>
            The height of the coordinate space in CSS pixels.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  
  <xs:element name="form">
    <xs:annotation>
      <xs:documentation>
        The HTML <form> element represents a document section that contains interactive controls to submit information to a web server.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="blocks-with-text"/>
      </xs:choice>
      <xs:attributeGroup ref="coreattrs"/>
      <xs:attribute name="action" type="xs:anyURI">
        <xs:annotation>
          <xs:documentation>
            The URI of a program that processes the form information.
            
            In HTML5, the action attribute is no longer required.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute default="get" name="method">
        <xs:annotation>
          <xs:documentation>
            The HTTP method that the browser uses to submit the form. Possible values are:
            
            - post: Corresponds to the HTTP POST method ; form data are included in the body of the form and sent to the server.
            
            - get: Corresponds to the HTTP GET method; form data are appended to the action attribute URI with a '?' as separator, and the resulting URI is sent to the server. Use this method when the form has no side-effects and contains only ASCII characters.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="get"/>
            <xs:enumeration value="post"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute default="application/x-www-form-urlencoded" name="enctype" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            When the value of the method attribute is post, enctype is the MIME type of content that is used to submit the form to the server. Possible values are:
            
            - application/x-www-form-urlencoded: The default value if the attribute is not specified.
            - multipart/form-data: The value used for an <input> element with the type attribute set to "file".
            - text/plain (HTML5)
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="accept-charset" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            A space- or comma-delimited list of character encodings that the server accepts. The browser uses them in the order in which they are listed. The default value, the reserved string "UNKNOWN", indicates the same encoding as that of the document containing the form element.
            
            In previous versions of HTML, the different character encodings could be delimited by spaces or commas. In HTML5, only spaces are allowed as delimiters.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="name" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            The name of the form. In HTML 4, its use is deprecated (id should be used instead). It must be unique among the forms in a document and not just an empty string in HTML 5.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="accept" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            A comma-separated list of content types that the server accepts.
            
            Usage note: This attribute has been removed in HTML5 and should no longer be used. Instead, use the accept attribute of the specific <input> element.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="onsubmit" type="xs:string"/>
      <xs:attribute name="onreset" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="label">
    <xs:annotation>
      <xs:documentation>
        The HTML <label> Element represents a caption for an item in a user interface. It can be associated with a control either by placing the control element inside the label element, or by using the for attribute. Such a control is called the labeled control of the label element.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="inlineBaseType">
          <xs:attribute name="for" type="xs:IDREF"/>
          <xs:attribute name="accesskey">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:length fixed="true" value="1"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:attribute>
          <xs:attribute name="onfocus" type="xs:string"/>
          <xs:attribute name="onblur" type="xs:string"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>
  <xs:simpleType name="InputType">
    <xs:restriction base="xs:token">
      <xs:enumeration value="text"/>
      <xs:enumeration value="password"/>
      <xs:enumeration value="checkbox"/>
      <xs:enumeration value="radio"/>
      <xs:enumeration value="submit"/>
      <xs:enumeration value="reset"/>
      <xs:enumeration value="file"/>
      <xs:enumeration value="hidden"/>
      <xs:enumeration value="image"/>
      <xs:enumeration value="button"/>
    </xs:restriction>
  </xs:simpleType>
  <xs:element name="input">
    <xs:annotation>
      <xs:documentation>
        The HTML <input> element is used to create interactive controls for web-based forms in order to accept data from user. 
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attributeGroup ref="coreattrs"/>
      <xs:attribute default="text" name="type" type="InputType"/>
      <xs:attribute name="name">
        <xs:annotation>
          <xs:documentation>
            the name attribute is required for all but submit & reset
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
      <xs:attribute name="value"/>
      <xs:attribute name="checked">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="checked"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="disabled">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="disabled"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="readonly">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="readonly"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="size"/>
      <xs:attribute name="maxlength" type="xs:nonNegativeInteger"/>
      <xs:attribute name="src" type="xs:anyURI"/>
      <xs:attribute name="alt"/>
      <xs:attribute name="usemap" type="xs:anyURI"/>
      <xs:attribute name="onselect" type="xs:string"/>
      <xs:attribute name="onchange" type="xs:string"/>
      <xs:attribute name="accept" type="xs:string"/>
      <xs:attribute name="onclick" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Javascript event handler content attribute for the "click" event.
            
            Warning: event handler content attributes should be avoided. They make the markup bigger and less readable. Concerns of content/structure and behavior are not well-separated, making a bug harder to find. Furthermore, usage of event attributes almost always causes scripts to expose global functions on the Window object, polluting the global namespace.
            
            The EventTarget.addEventListener() function should be used instead to add a listener for the event.
          </xs:documentation>
        </xs:annotation>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  <xs:element name="select">
    <xs:annotation>
      <xs:documentation>
        The HTML select (<select>) element represents a control that presents a menu of options. The options within the menu are represented by <option> elements, which can be grouped by <optgroup> elements. Options can be pre-selected for the user.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded">
        <xs:element ref="optgroup"/>
        <xs:element ref="option"/>
      </xs:choice>
      <xs:attribute name="name"/>
      <xs:attribute name="size" type="xs:nonNegativeInteger"/>
      <xs:attribute name="multiple">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="multiple"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="disabled">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="disabled"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="tabindex" type="xs:nonNegativeInteger"/>
      <xs:attribute name="onfocus" type="xs:string"/>
      <xs:attribute name="onblur" type="xs:string"/>
      <xs:attribute name="onchange" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="optgroup">
    <xs:annotation>
      <xs:documentation>
        In a Web form, the HTML <optgroup> element creates a grouping of options within a <select> element.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" ref="option"/>
      </xs:sequence>
      <xs:attribute name="disabled">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="disabled"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="label" type="xs:string" use="required"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="option">
    <xs:annotation>
      <xs:documentation>
        In a Web form, the HTML <option> element is used to create a control representing an item within a <select>, an <optgroup> or a <datalist> HTML5 element.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:attribute name="selected">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="selected"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="disabled">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="disabled"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="label" type="xs:string"/>
      <xs:attribute name="value"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="textarea">
    <xs:annotation>
      <xs:documentation>
        The HTML <textarea> element represents a multi-line plain-text editing control.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:attribute name="name"/>
      <xs:attribute name="rows" type="xs:nonNegativeInteger" use="required"/>
      <xs:attribute name="cols" type="xs:nonNegativeInteger" use="required"/>
      <xs:attribute name="disabled">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="disabled"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="readonly">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="readonly"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="onselect" type="xs:string"/>
      <xs:attribute name="onchange" type="xs:string"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="fieldset">
    <xs:annotation>
      <xs:documentation>
        The HTML <fieldset> element is used to group several controls as well as labels (<label>) within a web form.
        
        Only one legend element should occur in the content, and if present should only be preceded by whitespace.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:sequence>
        <xs:element ref="legend"/>
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:group ref="text-only"/>
        </xs:choice>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="legend">
    <xs:annotation>
      <xs:documentation>
        The HTML <legend> Element (or HTML Legend Field Element) represents a caption for the content of its parent <fieldset>.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="inlineBaseType">
          <xs:attribute name="accesskey">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:length fixed="true" value="1"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:attribute>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>
  <xs:element name="button">
    <xs:annotation>
      <xs:documentation>
        The HTML <button> Element represents a clickable button.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:group ref="text-only"/>
      </xs:choice>
      <xs:attribute name="name"/>
      <xs:attribute name="value"/>
      <xs:attribute default="submit" name="type">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="button"/>
            <xs:enumeration value="submit"/>
            <xs:enumeration value="reset"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="disabled">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="disabled"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  
  <xs:element name="iframe">
    <xs:annotation>
      <xs:documentation>
        The HTML <iframe> Element (or HTML inline frame element) represents a nested browsing context, effectively embedding another HTML page into the current page.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:attributeGroup ref="coreattrs"/>
      <xs:attribute name="name" type="xs:NMTOKEN"/>
      <xs:attribute name="src" type="xs:anyURI"/>
      <xs:attribute default="1" name="frameborder">
        <xs:annotation>
          <xs:documentation>
            Warning: HTML 4 only
            
            The value 1 (the default) tells the browser to draw a border between this frame and every other frame.
            The value 0 tells the browser not to draw a border between this frame and other frames.
          </xs:documentation>
        </xs:annotation>
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="1"/>
            <xs:enumeration value="0"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="height" type="htmlLength"/>
      <xs:attribute name="width" type="htmlLength"/>
      <xs:attribute name="allowfullscreen">
        <xs:simpleType>
          <xs:restriction base="xs:string">
            <xs:enumeration value="allowfullscreen"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
    </xs:complexType>
  </xs:element>
  
</xs:schema>
Index: modules/damieng/graphical_editor/loncapa_daxe/web/config/loncapa_config.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/config/loncapa_config.xml
<?xml version="1.0" encoding="ISO-8859-1"?><CONFIG_JAXE>
  <LANGAGE>
    <FICHIER_SCHEMA nom="loncapa.xsd"/>
    <RACINE element="problem"/>
    <RACINE element="html"/>
    <RACINE element="library"/>
    <RACINE element="Task"/>
  </LANGAGE>
  
  <MENUS>
    <MENU nom="Structure">
      <MENU_INSERTION nom="problem"/>
      <MENU_INSERTION nom="library"/>
      <MENU_INSERTION nom="part"/>
      <MENU_INSERTION nom="scriptlib"/>
      <MENU_INSERTION nom="parserlib"/>
      <MENU_INSERTION nom="import"/>
      <MENU_INSERTION nom="block"/>
      <MENU_INSERTION nom="while"/>
      <MENU_INSERTION nom="problemtype"/>
      <MENU_INSERTION nom="preduedate"/>
      <MENU_INSERTION nom="postanswerdate"/>
      <MENU_INSERTION nom="randomlist"/>
      <MENU_INSERTION nom="solved"/>
      <MENU_INSERTION nom="notsolved"/>
      <MENU_INSERTION nom="print"/>
      <MENU_INSERTION nom="web"/>
      <MENU_INSERTION nom="standalone"/>
      <MENU_INSERTION nom="meta"/>
    </MENU>
    <MENU nom="Responses">
      <MENU nom="Text">
        <MENU_INSERTION nom="stringresponse"/>
        <MENU_INSERTION nom="essayresponse"/>
      </MENU>
      <MENU nom="Options">
        <MENU_INSERTION nom="radiobuttonresponse"/>
        <MENU_INSERTION nom="rankresponse"/>
        <MENU nom="Match response">
          <MENU_INSERTION nom="matchresponse"/>
          <MENU_INSERTION nom="itemgroup"/>
          <MENU_INSERTION nom="item"/>
        </MENU>
        <MENU nom="Option response">
          <MENU_INSERTION nom="optionresponse"/>
          <MENU_INSERTION nom="drawoptionlist"/>
        </MENU>
      </MENU>
      <MENU nom="Image">
        <MENU_INSERTION nom="imageresponse"/>
        <MENU_INSERTION nom="text"/>
        <MENU_INSERTION nom="image"/>
        <MENU_INSERTION nom="rectangle"/>
        <MENU_INSERTION nom="polygon"/>
      </MENU>
      <MENU nom="Math">
        <MENU_INSERTION nom="numericalresponse"/>
        <MENU_INSERTION nom="formularesponse"/>
        <MENU_INSERTION nom="answergroup"/>
        <MENU_INSERTION nom="value"/>
        <MENU_INSERTION nom="vector"/>
        <MENU_INSERTION nom="mathresponse"/>
      </MENU>
      <MENU nom="Chemistry">
        <MENU_INSERTION nom="organicresponse"/>
        <MENU_INSERTION nom="reactionresponse"/>
      </MENU>
      <MENU nom="Function plot">
        <MENU_INSERTION nom="functionplotresponse"/>
        <MENU_INSERTION nom="spline"/>
        <MENU_INSERTION nom="backgroundplot"/>
        <MENU_INSERTION nom="plotobject"/>
        <MENU_INSERTION nom="plotvector"/>
        <MENU_INSERTION nom="drawvectorsum"/>
        <MENU_INSERTION nom="functionplotruleset"/>
        <MENU_INSERTION nom="functionplotelements"/>
        <MENU_INSERTION nom="functionplotrule"/>
        <MENU_INSERTION nom="functionplotvectorrule"/>
        <MENU_INSERTION nom="functionplotvectorsumrule"/>
        <MENU_INSERTION nom="functionplotcustomrule"/>
      </MENU>
      <MENU nom="Advanced responses">
        <MENU_INSERTION nom="customresponse"/>
        <MENU_INSERTION nom="externalresponse"/>
        <MENU_INSERTION nom="dataresponse"/>
      </MENU>
      <MENU nom="Hints">
        <MENU_INSERTION nom="hintgroup"/>
        <MENU_INSERTION nom="hintpart"/>
        <MENU_INSERTION nom="stringhint"/>
        <MENU_INSERTION nom="radiobuttonhint"/>
        <MENU_INSERTION nom="optionhint"/>
        <MENU_INSERTION nom="numericalhint"/>
        <MENU_INSERTION nom="formulahint"/>
        <MENU_INSERTION nom="mathhint"/>
        <MENU_INSERTION nom="organichint"/>
        <MENU_INSERTION nom="reactionhint"/>
        <MENU_INSERTION nom="customhint"/>
      </MENU>
      <MENU nom="Common">
        <MENU_INSERTION nom="foilgroup"/>
        <MENU_INSERTION nom="conceptgroup"/>
        <MENU_INSERTION nom="foil"/>
        <MENU_INSERTION nom="answer"/>
        <MENU_INSERTION nom="textfield"/>
        <MENU_INSERTION nom="textline"/>
        <MENU_INSERTION nom="hiddensubmission"/>
        <MENU_INSERTION nom="hiddenline"/>
        <MENU_INSERTION nom="responseparam"/>
      </MENU>
    </MENU>
    <MENU nom="Block">
      <MENU nom="Scripts">
        <MENU_INSERTION nom="script"/>
      </MENU>
      <MENU nom="Gnuplot">
        <MENU_INSERTION nom="gnuplot"/>
        <MENU_INSERTION nom="title"/>
        <MENU_INSERTION nom="axis"/>
        <MENU_INSERTION nom="curve"/>
        <MENU_INSERTION nom="key"/>
        <MENU_INSERTION nom="xtics"/>
        <MENU_INSERTION nom="ytics"/>
        <MENU_INSERTION nom="tic"/>
        <MENU_INSERTION nom="xlabel"/>
        <MENU_INSERTION nom="ylabel"/>
        <MENU_INSERTION nom="function"/>
        <MENU_INSERTION nom="data"/>
      </MENU>
      <MENU nom="Random label">
        <MENU_INSERTION nom="randomlabel"/>
        <MENU_INSERTION nom="labelgroup"/>
        <MENU_INSERTION nom="location"/>
        <MENU_INSERTION nom="label"/>
        <MENU_INSERTION nom="bgimg"/>
      </MENU>
      <MENU nom="Draw image">
        <MENU_INSERTION nom="drawimage"/>
        <MENU_INSERTION nom="line"/>
        <MENU_INSERTION nom="rectangle"/>
        <MENU_INSERTION nom="arc"/>
        <MENU_INSERTION nom="fill"/>
        <MENU_INSERTION nom="polygon"/>
        <MENU_INSERTION nom="image"/>
      </MENU>
      <MENU nom="Tasks">
        <MENU_INSERTION nom="Task"/>
        <MENU_INSERTION nom="IntroParagraph"/>
        <MENU_INSERTION nom="ClosingParagraph"/>
        <MENU_INSERTION nom="Question"/>
        <MENU_INSERTION nom="QuestionText"/>
        <MENU_INSERTION nom="Setup"/>
        <MENU_INSERTION nom="Instance"/>
        <MENU_INSERTION nom="InstanceText"/>
        <MENU_INSERTION nom="Criteria"/>
        <MENU_INSERTION nom="CriteriaText"/>
        <MENU_INSERTION nom="GraderNote"/>
      </MENU>
      <MENU_INSERTION nom="languageblock"/>
      <MENU_INSERTION nom="translated"/>
      <MENU_INSERTION nom="lang"/>
      <MENU_INSERTION nom="window"/>
      <MENU_INSERTION nom="windowlink"/>
      <MENU_INSERTION nom="togglebox"/>
      <MENU_INSERTION nom="instructorcomment"/>
      <MENU_INSERTION nom="comment"/>
      <MENU_INSERTION nom="organicstructure"/>
      <MENU_INSERTION nom="parameter"/>
      <MENU_INSERTION nom="allow"/>
    </MENU>
    <MENU nom="Text">
      <MENU_INSERTION nom="display"/>
      <MENU_INSERTION nom="m"/>
      <MENU_INSERTION nom="lm"/>
      <MENU_INSERTION nom="num"/>
      <MENU_INSERTION nom="algebra"/>
      <MENU_INSERTION nom="chem"/>
      <MENU_INSERTION nom="parse"/>
      <MENU_INSERTION nom="displaytitle"/>
      <MENU_INSERTION nom="displayduedate"/>
      <MENU_INSERTION nom="displayweight"/>
      <MENU_INSERTION nom="displaystudentphoto"/>
    </MENU>
    <MENU nom="HTML">
      <MENU_INSERTION nom="html"/>
      <MENU_INSERTION nom="head"/>
      <MENU_INSERTION nom="title"/>
      <MENU_INSERTION nom="body"/>
      <MENU_INSERTION nom="h1"/>
      <MENU_INSERTION nom="section"/>
      <MENU_INSERTION nom="div"/>
      <MENU_INSERTION nom="a"/>
      <MENU_INSERTION nom="em"/>
      <MENU_INSERTION nom="strong"/>
      <MENU_INSERTION nom="hr"/>
      <MENU_INSERTION nom="dl"/>
      <MENU_INSERTION nom="dt"/>
      <MENU_INSERTION nom="dd"/>
      <MENU_INSERTION nom="pre"/>
      <MENU_INSERTION nom="code"/>
      <MENU_INSERTION nom="img"/>
      <MENU_INSERTION nom="object"/>
      <MENU_INSERTION nom="video"/>
      <MENU_INSERTION nom="source"/>
      <MENU_INSERTION nom="audio"/>
      <MENU_INSERTION nom="map"/>
      <MENU_INSERTION nom="area"/>
      <MENU_INSERTION nom="script"/>
    </MENU>
  </MENUS>
  
  <AFFICHAGE_NOEUDS>
    <AFFICHAGE_ELEMENT element="loncapa" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="problem" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="library" type="lcdblock"/>
    
    <AFFICHAGE_ELEMENT element="stringresponse" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="essayresponse" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="radiobuttonresponse" type="radioresponse"/>
    <AFFICHAGE_ELEMENT element="foilgroup" type="foilgroup"/>
    <AFFICHAGE_ELEMENT element="conceptgroup" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="foil" type="foil"/>
    <AFFICHAGE_ELEMENT element="optionresponse" type="optionresponse"/>
    <AFFICHAGE_ELEMENT element="drawoptionlist" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="matchresponse" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="itemgroup" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="item" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="rankresponse" type="rankresponse"/>
    <AFFICHAGE_ELEMENT element="imageresponse" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="image" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="polygon" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="rectangle" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="text" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="numericalresponse" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="answergroup" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="answer" type="lcdblock">
      <PARAMETRE nom="police" valeur="Monospaced"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="vector" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="value" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="formularesponse" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="mathresponse" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="functionplotresponse" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="functionplotelements" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="spline" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="backgroundplot" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="plotobject" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="plotvector" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="drawvectorsum" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="functionplotruleset" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="functionplotrule" type="lcdblock">
      <AFFICHAGE_ATTRIBUT attribut="xinitiallabel">
        <VALEUR_SUGGEREE>start</VALEUR_SUGGEREE>
        <VALEUR_SUGGEREE>end</VALEUR_SUGGEREE>
      </AFFICHAGE_ATTRIBUT>
      <AFFICHAGE_ATTRIBUT attribut="xfinallabel">
        <VALEUR_SUGGEREE>end</VALEUR_SUGGEREE>
      </AFFICHAGE_ATTRIBUT>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="functionplotvectorrule" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="functionplotvectorsumrule" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="functionplotcustomrule" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="organicresponse" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="reactionresponse" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="customresponse" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="externalresponse" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="dataresponse" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="responseparam" type="parameter"/>
    
    <AFFICHAGE_ELEMENT element="textfield" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="textline" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="hiddensubmission" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="hiddenline" type="lcdblock"/>
    
    <AFFICHAGE_ELEMENT element="hintgroup" type="hintgroup"/>
    <AFFICHAGE_ELEMENT element="hintpart" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="stringhint" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="radiobuttonhint" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="optionhint" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="numericalhint" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="formulahint" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="mathhint" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="organichint" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="reactionhint" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="customhint" type="lcdblock"/>
    
    <AFFICHAGE_ELEMENT element="gnuplot" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="title" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="axis" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="curve" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="data" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="function" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="key" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="label" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="tic" type="string"/>
    <AFFICHAGE_ELEMENT element="xtics" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="ytics" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="xlabel" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="ylabel" type="lcdblock"/>
    
    <AFFICHAGE_ELEMENT element="randomlabel" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="bgimg" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="labelgroup" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="location" type="lcdblock"/>
    
    <AFFICHAGE_ELEMENT element="part" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="allow" type="vide">
      <PARAMETRE nom="titreAtt" valeur="src"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="parserlib" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="scriptlib" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="parameter" type="parameter"/>
    <AFFICHAGE_ELEMENT element="displaytitle" type="vide">
      <PARAMETRE nom="titreAtt" valeur="style"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="displayduedate" type="vide"/>
    <AFFICHAGE_ELEMENT element="preduedate" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="postanswerdate" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="solved" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="notsolved" type="lcdblock"/>
    
    <AFFICHAGE_ELEMENT element="import" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="block" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="while" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="tex" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="print" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="web" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="standalone" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="problemtype" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="randomlist" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="languageblock" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="translated" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="lang" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="window" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="windowlink" type="string"><PARAMETRE nom="attributsVisibles" valeur="true"/></AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="togglebox" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="instructorcomment" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="comment" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="organicstructure" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="drawimage" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="line" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="arc" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="fill" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="point" type="vide"/>
    
    <AFFICHAGE_ELEMENT element="display" type="string">
      <PARAMETRE nom="police" valeur="Monospaced"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="m" type="texmathjax"/>
    <AFFICHAGE_ELEMENT element="lm" type="lm"/>
    <AFFICHAGE_ELEMENT element="num" type="string">
      <PARAMETRE nom="attributsVisibles" valeur="true"/>
      <PARAMETRE nom="police" valeur="Monospaced"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="algebra" type="string">
      <PARAMETRE nom="attributsVisibles" valeur="true"/>
      <PARAMETRE nom="police" valeur="Monospaced"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="chem" type="string">
      <PARAMETRE nom="attributsVisibles" valeur="true"/>
      <PARAMETRE nom="police" valeur="Monospaced"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="parse" type="string">
      <PARAMETRE nom="attributsVisibles" valeur="true"/>
      <PARAMETRE nom="police" valeur="Monospaced"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="displayweight" type="vide"/>
    <AFFICHAGE_ELEMENT element="displaystudentphoto" type="vide"/>
    
    <AFFICHAGE_ELEMENT element="Task" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="IntroParagraph" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="ClosingParagraph" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="Question" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="QuestionText" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="Setup" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="Instance" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="InstanceText" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="Criteria" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="CriteriaText" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="GraderNote" type="lcdblock"/>
    
    <AFFICHAGE_ELEMENT element="html" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="head" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="base" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="meta" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="link" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="style" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="script" type="script">
      <PARAMETRE nom="police" valeur="Monospaced"/>
      <AFFICHAGE_ATTRIBUT attribut="type">
        <VALEUR_SUGGEREE>text/javascript</VALEUR_SUGGEREE>
        <VALEUR_SUGGEREE>loncapa/perl</VALEUR_SUGGEREE>
      </AFFICHAGE_ATTRIBUT>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="noscript" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="body" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="section" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="header" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="footer" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="aside" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="h1" type="lcdblock"><PARAMETRE nom="style" valeur="GRAS"/></AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="h2" type="lcdblock"><PARAMETRE nom="style" valeur="GRAS"/></AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="h3" type="lcdblock"><PARAMETRE nom="style" valeur="GRAS"/></AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="h4" type="lcdblock"><PARAMETRE nom="style" valeur="GRAS"/></AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="h5" type="lcdblock"><PARAMETRE nom="style" valeur="GRAS"/></AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="h6" type="lcdblock"><PARAMETRE nom="style" valeur="GRAS"/></AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="div" type="hiddendiv"/>
    <AFFICHAGE_ELEMENT element="p" type="hiddenp"/>
    <AFFICHAGE_ELEMENT element="ul" type="wlist">
      <PARAMETRE nom="type" valeur="ul"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="li" type="witem"/>
    <AFFICHAGE_ELEMENT element="ol" type="wlist">
      <PARAMETRE nom="type" valeur="ol"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="dl" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="dt" type="string"/>
    <AFFICHAGE_ELEMENT element="dd" type="string"/>
    <AFFICHAGE_ELEMENT element="table" type="tabletexte">
      <PARAMETRE nom="trTag" valeur="tr"/>
      <PARAMETRE nom="tdTag" valeur="td"/>
      <PARAMETRE nom="thTag" valeur="th"/>
      <PARAMETRE nom="colspanAttr" valeur="colspan"/>
      <PARAMETRE nom="rowspanAttr" valeur="rowspan"/>
      <PARAMETRE nom="alignAttr" valeur="align"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="caption" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="tr" type="zone"/>
    <AFFICHAGE_ELEMENT element="td" type="string"/>
    <AFFICHAGE_ELEMENT element="th" type="string">
      <PARAMETRE nom="style" valeur="GRAS"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="span" type="stylespan"/>
    <AFFICHAGE_ELEMENT element="a" type="anchor"/>
    <AFFICHAGE_ELEMENT element="strong" type="string">
      <PARAMETRE nom="style" valeur="GRAS"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="em" type="string">
      <PARAMETRE nom="style" valeur="ITALIQUE"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="b" type="style">
      <PARAMETRE nom="style" valeur="GRAS"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="i" type="style">
      <PARAMETRE nom="style" valeur="ITALIQUE"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="sup" type="style">
      <PARAMETRE nom="style" valeur="EXPOSANT"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="sub" type="style">
      <PARAMETRE nom="style" valeur="INDICE"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="pre" type="lcdblock">
      <PARAMETRE nom="police" valeur="Monospaced"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="code" type="string">
      <PARAMETRE nom="police" valeur="Monospaced"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="kbd" type="string">
      <PARAMETRE nom="police" valeur="Monospaced"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="samp" type="string">
      <PARAMETRE nom="police" valeur="Monospaced"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="cite" type="string"/>
    <AFFICHAGE_ELEMENT element="q" type="string"/>
    <AFFICHAGE_ELEMENT element="tt" type="style">
      <PARAMETRE nom="police" valeur="Monospaced"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="ins" type="string"/>
    <AFFICHAGE_ELEMENT element="del" type="string"/>
    <AFFICHAGE_ELEMENT element="var" type="string">
      <PARAMETRE nom="style" valeur="ITALIQUE"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="small" type="string">
      <PARAMETRE nom="taille" valeur="75%"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="big" type="string">
      <PARAMETRE nom="taille" valeur="150%"/>
    </AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="br" type="br"/>
    <AFFICHAGE_ELEMENT element="hr" type="hr"/>
    <AFFICHAGE_ELEMENT element="address" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="blockquote" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="img" type="vide"><PARAMETRE nom="attributsVisibles" valeur="true"/></AFFICHAGE_ELEMENT>
    <AFFICHAGE_ELEMENT element="figure" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="figcaption" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="object" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="param" type="vide"/>
    <AFFICHAGE_ELEMENT element="embed" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="applet" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="video" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="source" type="string"/>
    <AFFICHAGE_ELEMENT element="audio" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="map" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="area" type="vide"/>
    <AFFICHAGE_ELEMENT element="canvas" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="form" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="input" type="vide"/>
    <AFFICHAGE_ELEMENT element="select" type="string"/>
    <AFFICHAGE_ELEMENT element="optgroup" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="option" type="string"/>
    <AFFICHAGE_ELEMENT element="textarea" type="string"/>
    <AFFICHAGE_ELEMENT element="fieldset" type="lcdblock"/>
    <AFFICHAGE_ELEMENT element="legend" type="string"/>
    <AFFICHAGE_ELEMENT element="button" type="string"/>
    <AFFICHAGE_ELEMENT element="iframe" type="lcdblock"/>
  </AFFICHAGE_NOEUDS>
  
  <STRINGS langue="en">
    
    <DESCRIPTION_CONFIG>LON-CAPA</DESCRIPTION_CONFIG>
    
    <STRINGS_ELEMENT element="problem">
      <TITRE>Problem</TITRE>
      <DOCUMENTATION>
        Root for .problem documents.
        
        This must be first in the file. It sets up the header of the webpage and generates the submit buttons. It also handles due dates properly.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="library">
      <TITRE>Library</TITRE>
      <DOCUMENTATION>
        Root for .library documents.
        A LON-CAPA .library file can contain just a script block, or just response items, or both.
        Library content is loaded into a problem statement by using an <import> tag.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    
    <STRINGS_ELEMENT element="stringresponse">
      <TITRE>Response: String</TITRE>
      <DOCUMENTATION>
        Query for a string.
        An internal textline tag is necessary for the student’s response to go in. It can check the string for either case or order.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
        <DOCUMENTATION>
          Unique identifier for the response in the document. If this isn’t set, it will be set during the publication step. It is used to assign parameter names in a way that can be tracked if an instructor modifies by hand.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
        <DOCUMENTATION>
          If set, the name will be used by the resource assembly tool when one is modifying parameters.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="answer">
        <TITRE>answer</TITRE>
        <DOCUMENTATION>
          the correct answer, either a perl list or scalar
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="type">
        <TITRE>type</TITRE>
        <DOCUMENTATION>
          Specifies how the string is checked (like the CAPA styles).
        </DOCUMENTATION>
        <TITRE_VALEUR valeur="cs">case sensitive, order important</TITRE_VALEUR>
        <TITRE_VALEUR valeur="ci">case insensitive, order important</TITRE_VALEUR>
        <TITRE_VALEUR valeur="mc">case insensitive, order unimportant</TITRE_VALEUR>
        <TITRE_VALEUR valeur="re">regular expression</TITRE_VALEUR>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="answerdisplay">
        <TITRE>answer display</TITRE>
        <DOCUMENTATION>
          String to display for answer
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="preprocess">
        <TITRE>pre-processor subroutine</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="essayresponse">
      <TITRE>Response: Essay</TITRE>
      <DOCUMENTATION>
        Query for a long text or a line, possibly with spell checking.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
        <DOCUMENTATION>
          Unique identifier for the response in the document. If this isn’t set, it will be set during the publication step. It is used to assign parameter names in a way that can be tracked if an instructor modifies by hand.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
        <DOCUMENTATION>
          If set, the name will be used by the resource assembly tool when one is modifying parameters.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="radiobuttonresponse">
      <TITRE>Response: One of N statements</TITRE>
      <DOCUMENTATION>
        Query for a single choice among several statements.
        The value of the foils can only be true, false, or unused.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
        <DOCUMENTATION>
          Unique identifier for the response in the document. If this isn’t set, it will be set during the publication step. It is used to assign parameter names in a way that can be tracked if an instructor modifies by hand.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
        <DOCUMENTATION>
          If set, the name will be used by the resource assembly tool when one is modifying parameters.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="max">
        <TITRE>max number of shown foils</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="randomize">
        <TITRE>randomize foil order</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="direction">
        <TITRE>display direction</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="foilgroup">
      <TITRE>Collection of Foils</TITRE>
      <STRINGS_ATTRIBUT attribut="options">
        <TITRE>options</TITRE>
        <DOCUMENTATION>
          Perl list of possible foil values.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="texoptions">
        <TITRE>print options</TITRE>
        <TITRE_VALEUR valeur="nochoice">nochoice</TITRE_VALEUR>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="checkboxvalue">
        <TITRE>two-option checkboxes for</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="checkboxoptions">
        <TITRE>checkbox options</TITRE>
        <TITRE_VALEUR valeur="nochoice">nochoice</TITRE_VALEUR>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="conceptgroup">
      <TITRE>Collection of similar foils</TITRE>
      <DOCUMENTATION>
        Collection of similar foils.
        When a problem is displayed, only one of the contained foils is selected for display.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="foil">
      <TITRE>Foil</TITRE>
      <DOCUMENTATION>
        Foils do not need to be text; they can be images or other resources.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="value">
        <TITRE>value</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="location">
        <TITRE>location</TITRE>
        <TITRE_VALEUR valeur="random">random</TITRE_VALEUR>
        <TITRE_VALEUR valeur="top">top</TITRE_VALEUR>
        <TITRE_VALEUR valeur="bottom">bottom</TITRE_VALEUR>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="optionresponse">
      <TITRE>Response: Select from Options</TITRE>
      <DOCUMENTATION>
        Query for a choice for each given statement.
        
        Option Response problems present foils to the student with drop-down boxes. The student can select the matching choice for the foils from a list of choices. Optionally, the foils may be bundled into Concept Groups and the system will select one foil from each group to display to the student.
        
        By default, the list of options is presented in front of the foils. Using the optional drawoptionlist element, the list of options can be embedded into the foil. 
        
        The foilgroup is required to have an options attribute which should be a perl list of possible options for the student.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
        <DOCUMENTATION>
          Unique identifier for the response in the document. If this isn’t set, it will be set during the publication step. It is used to assign parameter names in a way that can be tracked if an instructor modifies by hand.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
        <DOCUMENTATION>
          If set, the name will be used by the resource assembly tool when one is modifying parameters.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="max">
        <TITRE>max number of shown foils</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="randomize">
        <TITRE>randomize foil order</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="TeXlayout">
        <TITRE>display of options when printed</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="drawoptionlist">
      <TITRE>Draw Option List</TITRE>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="matchresponse">
      <TITRE>Response: Match Two Lists</TITRE>
      <DOCUMENTATION>
        Query for matches betweens items from two lists.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
        <DOCUMENTATION>
          Unique identifier for the response in the document. If this isn’t set, it will be set during the publication step. It is used to assign parameter names in a way that can be tracked if an instructor modifies by hand.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
        <DOCUMENTATION>
          If set, the name will be used by the resource assembly tool when one is modifying parameters.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="max">
        <TITRE>max number of shown foils</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="randomize">
        <TITRE>randomize foil order</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="itemgroup">
      <TITRE>Items to Match</TITRE>
      <STRINGS_ATTRIBUT attribut="randomize">
        <TITRE>randomize order</TITRE>
        <TITRE_VALEUR valeur="yes">yes</TITRE_VALEUR>
        <TITRE_VALEUR valeur="no">no</TITRE_VALEUR>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="location">
        <TITRE>items display location</TITRE>
        <TITRE_VALEUR valeur="top">top</TITRE_VALEUR>
        <TITRE_VALEUR valeur="bottom">bottom</TITRE_VALEUR>
        <TITRE_VALEUR valeur="left">left</TITRE_VALEUR>
        <TITRE_VALEUR valeur="right">right</TITRE_VALEUR>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="direction">
        <TITRE>items display direction</TITRE>
        <TITRE_VALEUR valeur="vertical">vertical</TITRE_VALEUR>
        <TITRE_VALEUR valeur="horizontal">horizontal</TITRE_VALEUR>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="columns">
        <TITRE>items columns</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="TeXitemgroupwidth">
        <TITRE>TeX itemgroup width</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="item">
      <TITRE>Item</TITRE>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="location">
        <TITRE>location</TITRE>
        <TITRE_VALEUR valeur="random">random</TITRE_VALEUR>
        <TITRE_VALEUR valeur="top">top</TITRE_VALEUR>
        <TITRE_VALEUR valeur="bottom">bottom</TITRE_VALEUR>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="rankresponse">
      <TITRE>Response: Rank Values</TITRE>
      <DOCUMENTATION>
        Query to sort a list of items in the right order.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
        <DOCUMENTATION>
          Unique identifier for the response in the document. If this isn’t set, it will be set during the publication step. It is used to assign parameter names in a way that can be tracked if an instructor modifies by hand.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
        <DOCUMENTATION>
          If set, the name will be used by the resource assembly tool when one is modifying parameters.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="max">
        <TITRE>max number of shown foils</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="randomize">
        <TITRE>randomize foil order</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="imageresponse">
      <TITRE>Response: Click on Image</TITRE>
      <DOCUMENTATION>
        Query for the selection of an image in a list.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
        <DOCUMENTATION>
          Unique identifier for the response in the document. If this isn’t set, it will be set during the publication step. It is used to assign parameter names in a way that can be tracked if an instructor modifies by hand.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
        <DOCUMENTATION>
          If set, the name will be used by the resource assembly tool when one is modifying parameters.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="max">
        <TITRE>max number of shown foils</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="image">
      <TITRE>Image</TITRE>
      <DOCUMENTATION>
        Image (contains the image source file).
        The delimited text should correspond to a published image resource.
        Example: <image>/res/adm/includes/templates/man1.jpg</image>. The following image formats are recommended - gif, jpg or png. Other formats may work, but there may be printing or display issues. The image should only appear once per foil.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="polygon">
      <TITRE>Polygonal area in image</TITRE>
      <DOCUMENTATION>
        When inside an Image Response, this element contains a list of coordinates.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="rectangle">
      <TITRE>Rectangular area in image</TITRE>
      <DOCUMENTATION>
        When inside an Image Response, this element defines a rectangular area in the image (contains coordinate pairs).
        The delimited text specifies a rectangular area that is correct, specified as (x1,y1)-(x2,y2), where x1, x2, y1, and y2 are number corresponding to the x and y coordinates of two corners that define a rectangle which specifies where the right answer for this foil is located on the image. For example, (0,0)-(100,200) will specify that a rectangle 100 pixels wide and 200 pixels tall, situated in the upper left of the image, is correct. At least one rectangle is required; multiple rectangles may be specified.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="text">
      <TITRE>Text to describe option</TITRE>
      <DOCUMENTATION>
        When inside an Image Response:
        Text to describe option.
        The delimited text is printed before the image is shown on the screen.
        This text is typically used to describe to the student what they are expected to click on.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="numericalresponse">
      <TITRE>Response: Numerical</TITRE>
      <DOCUMENTATION>
        Query for one or several numbers, possibly with units.
        
        A tolerance parameter should be used to determine how closely the system will require the student’s answer to be in order to count it correct. The tolerance will default to zero if it is not defined. The tolerance parameter should always be defined for a numerical problem unless you are certain only integer answers are generated from your script and you want students to reply with exactly that integer.
        
        A significant figures parameter tells the system how many significant figures there are in the problem, as either a single number, e.g. 3, or a range of acceptable values, expressed as min,max. The system will check to make sure that the student’s answer contains this many significant digits, useful in many scientific calculations.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
        <DOCUMENTATION>
          Unique identifier for the response in the document. If this isn’t set, it will be set during the publication step. It is used to assign parameter names in a way that can be tracked if an instructor modifies by hand.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
        <DOCUMENTATION>
          If set, the name will be used by the resource assembly tool when one is modifying parameters.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="answer">
        <TITRE>answer</TITRE>
        <DOCUMENTATION>
          The answer the system is looking for. The answer can use variables calculated/defined in the problem’s script block, allowing the answer to be determined dynamically (including randomization).
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="incorrect">
        <TITRE>incorrect answers</TITRE>
        <DOCUMENTATION>
          When switched into exam ("bubble sheet") mode, LON-CAPA usually create wrong answers automatically. To specify wrong answers yourself, you need to provide an array of incorrect values in this attribute. You need to provide at least as many incorrects as 1 less than the number of bubbles on the exam. You can provide more if you want to.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="unit">
        <TITRE>unit</TITRE>
        <DOCUMENTATION>
          Expected units for the answer, with the LON-CAPA math syntax. For instance, "m/s^2" or "km/(A*hr)".
          LON-CAPA will convert units automatically when checking if a reply is correct, so for instance "15000 kW*s" will be accepted when the correct answer is "15 MJ".
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="format">
        <TITRE>format</TITRE>
        <DOCUMENTATION>
          You can format the number displayed by the computer as the answer. For instance, if the answer is one-third, the computer will display that it computed ".333333333" as the answer. If you'd like to shorten that, you can use the Format field. Format strings like "2E" (without the quotes) will display three significant digits in scientific notation. Format strings like "2f" will display two digits after the decimal point. Format strings like "2s" will round a number to 2 significant digits.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="preprocess">
        <TITRE>pre-processor subroutine</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="answergroup">
      <TITRE>Collection of Answers</TITRE>
      <STRINGS_ATTRIBUT attribut="type">
        <TITRE>type</TITRE>
        <TITRE_VALEUR valeur="ordered">ordered</TITRE_VALEUR>
        <TITRE_VALEUR valeur="unordered">unordered</TITRE_VALEUR>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="answer">
      <TITRE>Answer</TITRE>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="type">
        <TITRE>type</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="vector">
      <TITRE>Vector</TITRE>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="value">
      <TITRE>Value</TITRE>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="formularesponse">
      <TITRE>Response: Formula</TITRE>
      <DOCUMENTATION>Query for a formula.</DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
        <DOCUMENTATION>
          Unique identifier for the response in the document. If this isn’t set, it will be set during the publication step. It is used to assign parameter names in a way that can be tracked if an instructor modifies by hand.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
        <DOCUMENTATION>
          If set, the name will be used by the resource assembly tool when one is modifying parameters.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="answer">
        <TITRE>answer</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="samples">
        <TITRE>sample points</TITRE>
        <DOCUMENTATION>
          Format:
          1. A comma-separated list of the variables you wish to interpret,
          2. followed by “@” (not in quotes),
          3. followed by any number of the following two things, separated by semi-colons:
          (a) a comma-separated list of as many numbers as there are variables, which specifies one sampling point, OR
          (b) a comma-separated list of as many numbers as there are variables, followed by a colon, followed by another list of as many numbers as there are variables, followed by a #, followed by an integer.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="preprocess">
        <TITRE>pre-processor subroutine</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="mathresponse">
      <TITRE>Response: Math</TITRE>
      <DOCUMENTATION>
        Query for text that is evaluated with a script written in a computer algebra system language by the problem author.
        
        MathResponse is extremely powerful, as it tests answers for conditions rather than agreement with a particular correct answer. An unfortunate byproduct, however, is that it cannot be analyzed by several of the LON-CAPA statistics tools.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
        <DOCUMENTATION>
          Unique identifier for the response in the document. If this isn’t set, it will be set during the publication step. It is used to assign parameter names in a way that can be tracked if an instructor modifies by hand.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
        <DOCUMENTATION>
          If set, the name will be used by the resource assembly tool when one is modifying parameters.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="answerdisplay">
        <TITRE>string to display for answer</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="cas">
        <TITRE>algebra system</TITRE>
        <DOCUMENTATION>
          Maxima and R are supported.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="args">
        <TITRE>argument array</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="libraries">
        <TITRE>libraries</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="functionplotresponse">
      <TITRE>Response: Function Plot</TITRE>
      <DOCUMENTATION>
        Query for the drawing of a function.
        
        Requires that the student creates a plot that matches specified criteria.
        Examples can be functions that have certain slopes, curvature, maxima or minima at specified independent coordinate values. The students create their answer by dragging the curves and adjusting the slopes.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
        <DOCUMENTATION>
          Unique identifier for the response in the document. If this isn’t set, it will be set during the publication step. It is used to assign parameter names in a way that can be tracked if an instructor modifies by hand.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
        <DOCUMENTATION>
          If set, the name will be used by the resource assembly tool when one is modifying parameters.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="width">
        <TITRE>width (pixels)</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="height">
        <TITRE>height (pixels)</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="xlabel">
        <TITRE>label x-axis</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="xmin">
        <TITRE>minimum x-value</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="xmax">
        <TITRE>maximum x-value</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="xaxisvisible">
        <TITRE>x-axis visible</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="ylabel">
        <TITRE>label y-axis</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="ymin">
        <TITRE>minimum y-value</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="ymax">
        <TITRE>maximum y-value</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="yaxisvisible">
        <TITRE>y-axis visible</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="gridvisible">
        <TITRE>grid visible</TITRE>
        <DOCUMENTATION>
          This determines whether or not the grid is on the graph.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="answerdisplay">
        <TITRE>background plot(s) for answer</TITRE>
        <DOCUMENTATION>
          Background plot(s) for answer (function(x):xmin:xmax,function(x):xmin:xmax,x1:y1:sx1:sy1:x2:y2:sx2:sy2,...)
          
          This is a green curve the computer will display once the correct answer has been submitted. It is static, and can be given as a piecewise function.
          Since some problems will have multiple correct answers, this necessarily will only be a possible answer. Only the left hand side of the equation is necessary. For example, entering x + 2 will display the line y = x + 2.
          The syntax must be syntax recognized by GeoGebra. To test syntax for Geogebra directly, visit http://www.geogebra.org/webstart/geogebra.html .
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="functionplotelements">
      <TITRE>Function Plot Elements</TITRE>
      <DOCUMENTATION>Function Plot Elements</DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="spline">
      <TITRE>Spline</TITRE>
      <DOCUMENTATION>
        At least one spline is necessary for a graph problem. These splines are what will be adjusted and analyzed to solve the problem.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="index">
        <TITRE>index</TITRE>
        <DOCUMENTATION>
          This is the label assigned to the spline. In general, it's simplest just to label them A, B, C etc.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="order">
        <TITRE>order</TITRE>
        <DOCUMENTATION>
          This determines the number of Control Points on the spline. For example, selecting '3' means there will be 3 points on the spline that can be moved, as well as 3 points off the spline that will control the slope. 
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="initx">
        <TITRE>initial x-value</TITRE>
        <DOCUMENTATION>
          "Initial x-value" and "Initial y-value" determine where the left most Control Point will be.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="inity">
        <TITRE>initial y-value</TITRE>
        <DOCUMENTATION>
          "Initial x-value" and "Initial y-value" determine where the left most Control Point will be.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="scalex">
        <TITRE>scale x</TITRE>
        <DOCUMENTATION>
          This determines the right most location of the Control Points (on the spline). To figure out where this point will be, add 'Initial x-value' to 'Scale x'.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="scaley">
        <TITRE>scale y</TITRE>
        <DOCUMENTATION>
          This determines the distance (in the y-direction) between the Control Points on the spline, and the ones that control the slope.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="backgroundplot">
      <TITRE>Background Function Plot</TITRE>
      <DOCUMENTATION>
        This places a static curve on the graph of your choosing. It can be labeled, moveable or fixed, and any color desired. Only the right hand side of the function you want displayed is necessary. For example, entering x+2 will display the line y=x+2. The syntax must be syntax recognized by GeoGebra.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="function">
        <TITRE>function</TITRE>
        <DOCUMENTATION>
          An equals sign is not necessary. Just give the right hand side of the function. LON-CAPA variables are usable as well to allow individualized problems for each student. The syntax must be syntax recognized by GeoGebra.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="xinitial">
        <TITRE>initial x-value</TITRE>
        <DOCUMENTATION>
          Initial x-value (optional)
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="xfinal">
        <TITRE>final x-value</TITRE>
        <DOCUMENTATION>
          Final x-value (optional)
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="label">
        <TITRE>label on plot</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="color">
        <TITRE>color (hex code)</TITRE>
        <DOCUMENTATION>
          Color of the background function (hex code).
          The default is 000000 (black). It is recommended to choose a color other than green, since it is easily confused with being the answer.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="fixed">
        <TITRE>fixed location</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="plotobject">
      <TITRE>Plot Object</TITRE>
      <DOCUMENTATION>
        This places a point in the applet. Generally intended to be used with Vectors to create problems involving Free-Body Diagrams or any other points that vectors (or arrows) connect to and from.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="label">
        <TITRE>label on plot</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="x">
        <TITRE>x</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="y">
        <TITRE>y</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="plotvector">
      <TITRE>Plot Vector</TITRE>
      <DOCUMENTATION>
        This creates a vector (or arrow) in the applet. Generally intended to be used with Objects to create problems involving Free-Body Diagrams or to establish connections between Objects.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="label">
        <TITRE>label on plot</TITRE>
        <DOCUMENTATION>
          Determines the name of the vector, as well as the name that will be visible in the problem.
          This value MUST be capitalized and cannot include spaces or most symbols. To be safe, stick with letters and numbers.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="tailx">
        <TITRE>tailx</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="taily">
        <TITRE>taily</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="tipx">
        <TITRE>tipx</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="tipy">
        <TITRE>tipy</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="drawvectorsum">
      <TITRE>Draw Vector Sum</TITRE>
      <DOCUMENTATION>Draw Vector Sum</DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="label">
        <TITRE>label on plot</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="tailx">
        <TITRE>tailx</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="taily">
        <TITRE>taily</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="showvalue">
        <TITRE>show value</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="vectorlist">
        <TITRE>vector list</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="functionplotruleset">
      <TITRE>Function Plot Rule Set</TITRE>
      <DOCUMENTATION>
        This is where the rules are defined. These rules will determine whether or not an entered answer is correct or not. If there are no rules, any answer will be deemed correct. If there is more than one rule, when an answer is submitted, the server will analyze them in order until one of them is broken (of course, if it's a correct answer, it will go through all of them and return a green box). In such an event, any subsequent rules will be ignored. If conditional hints related to these rules are added, only the first broken rule's hint will be shown, even if all rules are broken.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="functionplotrule">
      <TITRE>Function Plot Graph Rule</TITRE>
      <DOCUMENTATION>
        Function Plot Graph Rule.
        
        Used to create a rule that determines whether or not a submitted graph is correct. In general, it takes the form of testing the function, its integral, or its first or second derivative over a given set of x-values. The test can be to see if it equals, is greater than, or less than a specified value. Anywhere a number is needed, a variable can also be used.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="index">
        <TITRE>index/name</TITRE>
        <DOCUMENTATION>
          This is an internal label for the rule. Something must be entered here, and it must be different for each rule. This same value will be used to add a conditional hint.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="derivativeorder">
        <TITRE>function</TITRE>
        <DOCUMENTATION>
          This determines what the server will be testing. For instance, choose 'First derivative' causes the server to evaluate the derivative of the entered answer over the given domain.
        </DOCUMENTATION>
        <TITRE_VALEUR valeur="0">Function itself</TITRE_VALEUR>
        <TITRE_VALEUR valeur="1">First derivative</TITRE_VALEUR>
        <TITRE_VALEUR valeur="2">Second derivative</TITRE_VALEUR>
        <TITRE_VALEUR valeur="-1">Integral</TITRE_VALEUR>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="xinitial">
        <TITRE>initial x-value</TITRE>
        <DOCUMENTATION>
          A value must be entered for one of "Initial x-value" and "Initial x-value label". Either choose a numerical value for x (the first option), or choose the beginning of the submitted answer, the end, or a previously chosen named point.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="xinitiallabel">
        <TITRE>initial x-value label</TITRE>
        <DOCUMENTATION>
          A value must be entered for one of "Initial x-value" and "Initial x-value label". Either choose a numerical value for x (the first option), or choose the beginning of the submitted answer, the end, or a previously chosen named point.
        </DOCUMENTATION>
        <TITRE_VALEUR valeur="start">Start of Plot</TITRE_VALEUR>
        <TITRE_VALEUR valeur="end">End of Plot</TITRE_VALEUR>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="xfinal">
        <TITRE>final x-value</TITRE>
        <DOCUMENTATION>
          Final x-value (optional).
          This determines the end of the domain over which the rule examines. To test only a single point (the initial value), leave "Final x-value" and "Final x-value label" blank. If a label is entered, such as 'positive', the point at which the rule fails will be given this special label. This label can then be used in subsequent rules as an 'Initial x-value label'.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="xfinallabel">
        <TITRE>final x-value label</TITRE>
        <DOCUMENTATION>
          Final x-value label (optional).
          This determines the end of the domain over which the rule examines. To test only a single point (the initial value), leave "Final x-value" and "Final x-value label" blank. If a label is entered, such as 'positive', the point at which the rule fails will be given this special label. This label can then be used in subsequent rules as an 'Initial x-value label'.
        </DOCUMENTATION>
        <TITRE_VALEUR valeur="end">End of Plot</TITRE_VALEUR>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="minimumlength">
        <TITRE>minimum length for range</TITRE>
        <DOCUMENTATION>
          Minimum length for range (optional).
          This tests that the difference between the initial and final x-values are at least a certain length apart. This is only useful if there is at least one label.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="maximumlength">
        <TITRE>maximum length for range</TITRE>
        <DOCUMENTATION>
          Maximum length for range (optional).
          This tests that the difference between the initial and final x-values are at most a certain length apart. This is only useful if there is at least one label.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="relationship">
        <TITRE>relationship</TITRE>
        <DOCUMENTATION>
          The heart of the rule. This choice determines whether the chosen 'function' is greater than, less than, equal to, etc. a certain 'value'.
        </DOCUMENTATION>
        <TITRE_VALEUR valeur="eq">equal</TITRE_VALEUR>
        <TITRE_VALEUR valeur="ne">not equal</TITRE_VALEUR>
        <TITRE_VALEUR valeur="ge">greater than or equal</TITRE_VALEUR>
        <TITRE_VALEUR valeur="gt">greater than</TITRE_VALEUR>
        <TITRE_VALEUR valeur="lt">less than</TITRE_VALEUR>
        <TITRE_VALEUR valeur="le">less than or equal</TITRE_VALEUR>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="value">
        <TITRE>value</TITRE>
        <DOCUMENTATION>
          Enter the number you wish to compare to. It is also possible to choose 'not defined', in the event the answer should not have a value for the given domain. Within the value argument, the function itself can be evaluated using &fpr_f(), its derivative using &fpr_dfdx(), and its second derivative using &fpr_d2fdx2(). This allows for a comparison of two points on the graph. The value of a previously defined label can be retrieved using the function &fpr_val(), e.g., &fpr_val('positive'). Previous defined values from script blocks can also be retrieved as normal variables, e.g., $x.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="percenterror">
        <TITRE>percent error</TITRE>
        <DOCUMENTATION>
          This allows for a margin of error in the y-direction. For instance, if the rule requires that the derivative be equal to 5, the server will accept values close enough to 5 that are within the percent error defined here. Note: Choosing 10% would not mean that the answer is correct as long as it is within the range 4.5-5.5. Instead, the percent corresponds to the total size of the graph. For the function itself, the 'percent error' is multiplied by the ymax-ymin; for the first derivative, it's multiplied by (ymax-ymin)/(xmax-xmin); for the second derivative, it's multiplied by (ymax-ymin)/(xmax-xmin)2; and for the integral, it's multiplied by (ymax-ymin)*(xmax-xmin).
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="functionplotvectorrule">
      <TITRE>Function Plot Vector Rule</TITRE>
      <DOCUMENTATION>
        Function Plot Vector Rule
        Used to test whether vectors are in the right place, pointed in the right direction, and have the correct length.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="index">
        <TITRE>index/name</TITRE>
        <DOCUMENTATION>
          This is an internal label for the rule. This attribute must be different for each rule. This same value will be used to add a conditional hint.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="vector">
        <TITRE>vector</TITRE>
        <DOCUMENTATION>
          The name of one of the vectors in the list of function plot elements. Specifically, the one you want to test.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="attachpoint">
        <TITRE>attached to object</TITRE>
        <DOCUMENTATION>
          Object(s) this vector should be attached to. For more than one object, separate them by commas. If the vector should not be attached to any object, leave this blank. In this case, an object is considered attached if its tail OR its tip is in the vicinity of the object.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="notattachpoint">
        <TITRE>not attached to object</TITRE>
        <DOCUMENTATION>
          Object(s) that this vector should be not be near.
          For more than one object, separate them by commas. Particularly useful for distractor vectors.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="tailpoint">
        <TITRE>tail attached to object</TITRE>
        <DOCUMENTATION>
          Tail(s) this vector should be attached to.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="tippoint">
        <TITRE>tip attached to object</TITRE>
        <DOCUMENTATION>
          Tip(s) this vector should be attached to.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="nottailpoint">
        <TITRE>tail not attached to object</TITRE>
        <DOCUMENTATION>
          Tail(s) this vector should not be attached to.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="nottippoint">
        <TITRE>tip not attached to object</TITRE>
        <DOCUMENTATION>
          Tip(s) this vector should not be attached to.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="length">
        <TITRE>length</TITRE>
        <DOCUMENTATION>
          How long the vector should be.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="lengtherror">
        <TITRE>absolute error length</TITRE>
        <DOCUMENTATION>
          How accurate the length must be to get the answer correct.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="angle">
        <TITRE>angle</TITRE>
        <DOCUMENTATION>
          What direction should the vector point. All values are measured in degrees, counterclockwise starting at the positive x-axis. Values must be 0 ≤ θ < 360.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="angleerror">
        <TITRE>absolute error angle</TITRE>
        <DOCUMENTATION>
          How accurate the angle must be to get the answer correct.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="functionplotvectorsumrule">
      <TITRE>Function Plot Vector Sum Rule</TITRE>
      <DOCUMENTATION>
        Function Plot Vector Sum Rule
        Used to test the sum of a set of vectors.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="index">
        <TITRE>index/name</TITRE>
        <DOCUMENTATION>
          This is an internal label for the rule. It must be different for each rule. This same value will be used to add a conditional hint.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="vectors">
        <TITRE>comma-separated list of vectors</TITRE>
        <DOCUMENTATION>
          List all of the vectors that should be added up to be tested.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="length">
        <TITRE>sum vector length</TITRE>
        <DOCUMENTATION>
          How long the sum of these vectors should be.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="lengtherror">
        <TITRE>absolute error length</TITRE>
        <DOCUMENTATION>
          How accurate the length must be to get the answer correct.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="angle">
        <TITRE>sum vector angle</TITRE>
        <DOCUMENTATION>
          What direction should the sum of these vectors point. All values are measured in degrees, counterclockwise starting at the positive x-axis. Values must be 0 ≤ θ < 360.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="angleerror">
        <TITRE>absolute error angle</TITRE>
        <DOCUMENTATION>
          How accurate the angle must be to get the answer correct.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="functionplotcustomrule">
      <TITRE>Function Plot Custom Rule</TITRE>
      <DOCUMENTATION>
        Used to create rules that aren't options using the other rules. The coding is done in Perl and follows Perl syntax. Any variable written inside this rule will be recognized as normal and any evaluation function can be used as well.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="index">
        <TITRE>index/name</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="organicresponse">
      <TITRE>Response: Organic Chemical Structure</TITRE>
      <DOCUMENTATION>
        Query for an organic chemical structure with a molecular editor.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
        <DOCUMENTATION>
          Unique identifier for the response in the document. If this isn’t set, it will be set during the publication step. It is used to assign parameter names in a way that can be tracked if an instructor modifies by hand.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
        <DOCUMENTATION>
          If set, the name will be used by the resource assembly tool when one is modifying parameters.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="options">
        <TITRE>options</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="molecule">
        <TITRE>starting molecule</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="answer">
        <TITRE>correct answer</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="jmeanswer">
        <TITRE>JME string of the answer</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="width">
        <TITRE>width of correct answer image</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="reactionresponse">
      <TITRE>Response: Chemical Reaction</TITRE>
      <DOCUMENTATION>
        Query for a chemical reaction.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
        <DOCUMENTATION>
          Unique identifier for the response in the document. If this isn’t set, it will be set during the publication step. It is used to assign parameter names in a way that can be tracked if an instructor modifies by hand.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
        <DOCUMENTATION>
          If set, the name will be used by the resource assembly tool when one is modifying parameters.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="answer">
        <TITRE>answer</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="initial">
        <TITRE>initial reaction</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="customresponse">
      <TITRE>Response: Custom</TITRE>
      <DOCUMENTATION>
        Query for text without any constraint (any character is allowed). A script analyzes the answer to grade it automatically.
        The use of this response type is generally discouraged, since the responses will not be analyzable by the LON-CAPA statistics tools.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
        <DOCUMENTATION>
          Unique identifier for the response in the document. If this isn’t set, it will be set during the publication step. It is used to assign parameter names in a way that can be tracked if an instructor modifies by hand.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
        <DOCUMENTATION>
          If set, the name will be used by the resource assembly tool when one is modifying parameters.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="answerdisplay">
        <TITRE>string to display for answer</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="externalresponse">
      <TITRE>Response: External</TITRE>
      <DOCUMENTATION>
        Query for a long text or a line, sent to an external program for grading.
        
        The form sent will consist of:
        - LONCAPA student response full text of what the student entered in the entry field
        - LONCAPA correct answer contents of the answer attribute
        - LONCAPA language specified language encoding of the requesting resource
        - all items in the form attribute if any of these clash with the above, the above values will overwite the value in the form attribute
        
        The response of the remote server needs to be in XML as follows:
        - loncapagrade: takes no attributes, but must surround the response.
        - awarddetail: required. The delimited text inside must be one of the detailed results that appears in the data storage documentation. CVS:loncapa/doc/homework/datastorage, look for resource.partid.responseid.awarddetail.
        - message: optional message to have shown to the student.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
        <DOCUMENTATION>
          Unique identifier for the response in the document. If this isn’t set, it will be set during the publication step. It is used to assign parameter names in a way that can be tracked if an instructor modifies by hand.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
        <DOCUMENTATION>
          If set, the name will be used by the resource assembly tool when one is modifying parameters.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="url">
        <TITRE>url</TITRE>
        <DOCUMENTATION>
          url to submit the answer form to. It does not need to be a LON-CAPA machine.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="answer">
        <TITRE>answer</TITRE>
        <DOCUMENTATION>
          data to post in the form element LONCAPA correct answer to the remote site.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="form">
        <TITRE>form</TITRE>
        <DOCUMENTATION>
          hash variable name that will be submitted to the remote site as a HTTP form.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="answerdisplay">
        <TITRE>answer display</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="dataresponse">
      <TITRE>Response: Data</TITRE>
      <DOCUMENTATION>
        Query for text or numbers.
        Advanced type of response that implements a simple data storage and needs an input tag, such as textline, to work correctly.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="type">
        <TITRE>type</TITRE>
        <DOCUMENTATION>
          type of data stored in this response field. It should be one of the types supported by parameter.html
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="display">
        <TITRE>display</TITRE>
        <DOCUMENTATION>
          string that will be used to describe the field when interfacing with humans.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="responseparam">
      <TITRE>Response parameter</TITRE>
      <DOCUMENTATION>
        Parameters for a response
        
        Defines an externally adjustable parameter for the question, which the question can then use to allow other users to customize the problem for their courses without changing the source code of the problem.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="type">
        <TITRE>type</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="default">
        <TITRE>default</TITRE>
        <DOCUMENTATION>
          default value
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="description">
        <TITRE>description</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    
    <STRINGS_ELEMENT element="textfield">
      <TITRE>Large Text Entry Area</TITRE>
      <DOCUMENTATION>
        Large Text Entry Area, contains the text that appears by default
        
        Creates a large text input box. If data appears between the start and end tags, the data will appear in the textfield if the student has not yet made a submission.
        Additionally, it takes two attributes: rows and cols, which control the height and width of the text area respectively.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="rows">
        <TITRE>rows</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="cols">
        <TITRE>columns</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="addchars">
        <TITRE>click-on texts (comma sep)</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="spellcheck">
        <TITRE>spellcheck for</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="textline">
      <TITRE>Single Line Text Entry Area</TITRE>
      <DOCUMENTATION>
        Single Line Text Entry Area. Displays a field to enter text for a response.
        Should only be used inside stringresponse, numericalresponse, formularesponse, mathresponse, organicresponse, reactionresponse and customresponse.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="size">
        <TITRE>size</TITRE>
        <DOCUMENTATION>
          controls the width of the textline
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="addchars">
        <TITRE>click-on texts (comma sep)</TITRE>
        <DOCUMENTATION>
          Comma-separated list of characters or words that can be inserted with a click.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="readonly">
        <TITRE>read only</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="spellcheck">
        <TITRE>spellcheck</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="hiddensubmission">
      <TITRE>Hidden Submission</TITRE>
      <DOCUMENTATION>
        This creates a hidden form field with the given value.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="value">
        <TITRE>value</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="hiddenline">
      <TITRE>Hidden Line</TITRE>
      <DOCUMENTATION>
        This creates a hidden form field with the old response value.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    
    <STRINGS_ELEMENT element="hintgroup">
      <TITRE>Hint Group</TITRE>
      <DOCUMENTATION>
        The first part of the hint is the condition, which includes a specification of the foil(s) and foil answer(s) required to trigger the hint. The answers specified in the hint condition are compared with the user's submission, and if the condition is met, the hint action included in the conditional hint block will be executed (for example this could be the display of a block of text). You can set multiple hint conditions for a particular problem. Hint conditions are identified by a name. The corresponding hint action includes this hint condition name in the "on" parameter. When a hint condition evaluates to true, the corresponding hint action is triggered. Besides providing hint actions within <hintpart on="NAME"> </hintpart> tags for each named (NAME) hint condition, a hint can be designated for display if none of the conditional hints evaluate to true. The default hint is not displayed if the conditions were met for any of the conditional hints. The defau!
 lt hint action is included between <hintpart on="default"> </hintpart> tags.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="showoncorrect">
        <TITRE>showoncorrect</TITRE>
        <DOCUMENTATION>
          Show hint even if problem Correct
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="hintpart">
      <TITRE>Conditional Hint</TITRE>
      <DOCUMENTATION>
        Conditional Hint (hintpart)
        
        When a hint tag named the same as the on attribute evaluates to be correct, the hintpart will show.
        If no other hintpart is to show then all hintparts with an on value set to “default” will show.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="on">
        <TITRE>on</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="stringhint">
      <TITRE>String Hint Condition</TITRE>
      <DOCUMENTATION>
        String Hint Condition
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
        <DOCUMENTATION>
          Name of the hint condition.
          Should be set to the value of which hintpart will be shown.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="answer">
        <TITRE>answer</TITRE>
        <DOCUMENTATION>
          Text string.
          Should be set to the value of which hintpart will be shown.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="type">
        <TITRE>type</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="preprocess">
        <TITRE>preprocess</TITRE>
        <DOCUMENTATION>
          Pre-Processor Subroutine
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="radiobuttonhint">
      <TITRE>Radiobutton Hint Condition</TITRE>
      <DOCUMENTATION>
        Radiobutton Hint Condition
        
        The radiobutton hint tag takes two parameters: answer and name. The name is the name of the hint condition, and the answer is an array. The first element of the array will be 'foil'; the remaining elements are the names of the foils that you require to have been checked by the student for the hint to be displayed. For example, if you create a radiobutton response problem with six foils named: granite, gabbro, gneiss, shale, sandstone and schist, and you want your hint named: igneous to be displayed when either granite or basalt had been checked your radiobutton hint would be as follows:
        
        <radiobuttonhint answer="('foil','granite','gabbro')" name="igneous"></radiobuttonhint>
        
        In order to trigger display of this hint you also need to create a <hintpart> </hintpart> block that will include a textblock that contains the text of the actual hint.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
        <DOCUMENTATION>
          should be set to the value of which hintpart will be shown
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="answer">
        <TITRE>answer</TITRE>
        <DOCUMENTATION>
          should be at least a two element list: first the type (foil or concept) and then either the foil name(s) or the concept string(s), e.g., “(’foil’,’greaterthan’,’equal’)” if the condition should be triggered by the foils named “greaterthan” or “equal”
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="optionhint">
      <TITRE>Option Hint Condition</TITRE>
      <DOCUMENTATION>
        Option Response Hint Condition
        
        There are two types of option response hint conditions: one for standalone foils and one for concept groups. In both cases the option hint tag includes two parameters: answer and name for standalone foils, and concept and name for foils grouped together in a concept group. For the answer parameter, the names and submitted values for each of the foils that are being included in the hint condition are provided in a hash, i.e., in the format: ('Foil1'= > 'True','Foil2'= > 'False'). In the case of a conditional hint for a concept group, the format of the concept parameter is also a hash that links the name of each concept group included in the hint condition to either 'correct' or 'incorrect' - e.g., < optionhint concept="('buoyancy'= > 'correct','density'= > 'correct')" name="fluids" / > If 'correct' is specified for a named concept then when the conditional hint is evaluated answers for each of the foils selected by a student must be correct for the h!
 int action to be triggered. If anything other than 'correct' is provided in the concept hash in the optionhint tag then then students answers will be compared with the set answers for the foils in the concept group and as long as at least one answer is incorrect (i.e., the concept group was not correctly answered) then the corresponding hint action will be triggered. 
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
        <DOCUMENTATION>
          should be set to the value of which hintpart will be shown
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="answer">
        <TITRE>answer</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="concept">
        <TITRE>concept</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="numericalhint">
      <TITRE>Numerical Hint Condition</TITRE>
      <DOCUMENTATION>
        Numerical Hint Condition
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
        <DOCUMENTATION>
          Unique name given to the hint condition.
          Should be set to the value of which hintpart will be shown.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="answer">
        <TITRE>answer</TITRE>
        <DOCUMENTATION>
          Numerical answer for which the conditional is provided.
          Student submission of that answer in combination with the "unit" attribute in the hint condition will trigger the hint action specified in the <hintpart> element.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="unit">
        <TITRE>unit</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="format">
        <TITRE>format</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="preprocess">
        <TITRE>preprocess</TITRE>
        <DOCUMENTATION>
          Pre-Processor Subroutine
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="formulahint">
      <TITRE>Formula Hint Condition</TITRE>
      <DOCUMENTATION>
        Formula Hint Condition
        
        The formula submitted by the student is evaluated at the sample points for the hint and the calculated values are compared with the corresponding values determined by evaluating the "hint" answer at the same sampling points. A close correspondence between the two sets of values will trigger the hint action specified in the <hintpart> element.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
        <DOCUMENTATION>
          Unique name given to the hint condition.
          Should be set to the value of which hintpart will be shown.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="answer">
        <TITRE>answer</TITRE>
        <DOCUMENTATION>
          Formula answer for which the conditional is provided.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="samples">
        <TITRE>samples</TITRE>
        <DOCUMENTATION>
          Sample points (or range of points) over which sampling of the student’s submitted answer and the formula included in the formula hint answer parameter are to be compared. The syntax is the same as used to specify sampling points in the samples
          parameter of the formula reponse element itself.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="preprocess">
        <TITRE>preprocess</TITRE>
        <DOCUMENTATION>
          Pre-Processor Subroutine
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="mathhint">
      <TITRE>Math Hint Condition</TITRE>
      <DOCUMENTATION>Math Hint Condition</DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="cas">
        <TITRE>cas</TITRE>
        <DOCUMENTATION>
          Algebra System
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="args">
        <TITRE>args</TITRE>
        <DOCUMENTATION>
          Argument Array
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="organichint">
      <TITRE>Organic Hint Condition</TITRE>
      <DOCUMENTATION>Organic Hint Condition</DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="answer">
        <TITRE>answer</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="reactionhint">
      <TITRE>Reaction Hint Condition</TITRE>
      <DOCUMENTATION>Reaction Hint Condition</DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="answer">
        <TITRE>answer</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="customhint">
      <TITRE>Custom Hint Condition</TITRE>
      <DOCUMENTATION>
        Custom Hint Condition
        
        Define the hint condition within an answer block inside of the customhint block. The condition is defined like how an answer is defined in customresponse where you need to return EXACT ANS to indicate when customhint criteria are met.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
        <DOCUMENTATION>
          should be set to the value of which hintpart will be shown
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    
    <STRINGS_ELEMENT element="gnuplot">
      <TITRE>GnuPlot</TITRE>
      <DOCUMENTATION>
        The gnuplot LON-CAPA tag allows an author to design a plot which will be created programatically at the time when it is requested for display by a student. This is intended for use in homework problems where a distinct plot should be rendered for each student. It can be used in conjunction with a script to generate curve data for random plots.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="alttag">
        <TITRE>brief description</TITRE>
        <DOCUMENTATION>
          Brief description of the plot.
          This text is used as the alt value of the img tag used to display the plot on a web page.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="height">
        <TITRE>image height (pixels)</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="width">
        <TITRE>image width (pixels)</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="bgcolor">
        <TITRE>image background color</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="fgcolor">
        <TITRE>image foreground color</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="transparent">
        <TITRE>transparent image</TITRE>
        <DOCUMENTATION>
          If the image is transparent the background color will be ignored.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="grid">
        <TITRE>display grid</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="gridlayer">
        <TITRE>grid layer</TITRE>
        <DOCUMENTATION>
          Display grid front layer over filled boxes or filled curves
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="box_border">
        <TITRE>box border</TITRE>
        <DOCUMENTATION>
          Draw border for boxes
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="border">
        <TITRE>border</TITRE>
        <DOCUMENTATION>
          Draw border around plot
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="font">
        <TITRE>font</TITRE>
        <DOCUMENTATION>
          Font size to use in web output (in pts, or "small", "medium" or "large").
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="fontface">
        <TITRE>font face</TITRE>
        <DOCUMENTATION>
          Type of font to use
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="samples">
        <TITRE>samples</TITRE>
        <DOCUMENTATION>
          Number of samples for non-data plots.
          If a function element is used to specify the curve, this indicates the number of sample points to use.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="align">
        <TITRE>image alignment</TITRE>
        <DOCUMENTATION>
          Alignment for image in HTML
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="texwidth">
        <TITRE>texwidth</TITRE>
        <DOCUMENTATION>
          Width of plot when printed (mm)
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="texfont">
        <TITRE>texfont</TITRE>
        <DOCUMENTATION>
          Font size to use in TeX output (pts)
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="plotcolor">
        <TITRE>plot color</TITRE>
        <DOCUMENTATION>
          Color setting for printing
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="pattern">
        <TITRE>pattern</TITRE>
        <DOCUMENTATION>
          Pattern value for boxes
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="solid">
        <TITRE>boxes density of fill</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="fillstyle">
        <TITRE>boxes fill style</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="plottype">
        <TITRE>plot type</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="gridtype">
        <TITRE>grid type</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="lmargin">
        <TITRE>left margin width (pts)</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="rmargin">
        <TITRE>right margin width (pts)</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="tmargin">
        <TITRE>top margin width (pts)</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="bmargin">
        <TITRE>bottom margin width (pts)</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="boxwidth">
        <TITRE>boxes width</TITRE>
        <DOCUMENTATION>
          Width of boxes, default is auto
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="major_ticscale">
        <TITRE>size of major tic marks</TITRE>
        <DOCUMENTATION>
          Size of major tic marks (plot coordinates)
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="minor_ticscale">
        <TITRE>size of minor tic mark</TITRE>
        <DOCUMENTATION>
          Size of minor tic mark (plot coordinates)
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="title">
      <TITRE>Title</TITRE>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="axis">
      <TITRE>Axes</TITRE>
      <DOCUMENTATION>
        The Plot Axes tag allows you to specify the domain and range of the data to display. It is closely tied with the Plot Ticks tags, which specify where the gridlines are drawn on the plot.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="color">
        <TITRE>color of grid lines</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="xmin">
        <TITRE>minimum x-value shown in plot</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="xmax">
        <TITRE>maximum x-value shown in plot</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="ymin">
        <TITRE>minimum y-value shown in plot</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="ymax">
        <TITRE>maximum y-value shown in plot</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="xformat">
        <TITRE>x-axis number formatting</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="yformat">
        <TITRE>y-axis number formatting</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="xzero">
        <TITRE>show x-zero (y=0) axis</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="yzero">
        <TITRE>show y-zero (x=0) axis</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="curve">
      <TITRE>Curve</TITRE>
      <DOCUMENTATION>
        The curve tag is where you set the data to be plotted by gnuplot.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="color">
        <TITRE>color</TITRE>
        <DOCUMENTATION>
          Color of curve
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
        <DOCUMENTATION>
          Name of curve to appear in key
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="linestyle">
        <TITRE>line style</TITRE>
        <DOCUMENTATION>
          Unless otherwise noted the linestyles require only 2 data sets, X and Y.
          
          - lines: Connect adjacent points with straight line segments.
          - points: Display a small marker at each point.
          - linespoints: Draw both lines and points.
          Draws a small symbol at each point and then connects adjacent points with straight line segments.
          - dots: Place a tiny dots on the given points.
          - steps: Connect points with horizontal lines.
          This style connects consecutive points with two line segments: the first from (x1,y1) to (x2,y1) and the second from (x2,y1) to (x2,y2).
          - fsteps: Connect data with horizontal lines.
          This style connects consecutive points with two line segments: the first from (x1,y1) to (x1,y2) and the second from (x1,y2) to (x2,y2).
          - histeps: Plot as histogram.
          Y-values are assumed to be centered at the x-values; the point at x1 is represented as a horizontal line from ((x0+x1)/2,y1) to ((x1+x2)/2,y1). The lines representing the end points are extended so that the step is centered on at x. Adjacent points are connected by a vertical line at their average x, that is, from ((x1+x2)/2,y1) to ((x1+x2)/2,y2).
          - errorbars: Same as yerrorbars.
          - xerrorbars: Draw horizontal error bars around the points.
          Requires 3 or 4 data sets. Either X, Y, Xdelta or X, Y, Xlower, Xupper. Xdelta is a change relative to the given X value. The Xlower and Xupper values are absolute grid coordinates of the upper and lower values to indicated with error bars.
          - yerrorbars: Draw vertical error bars around the points.
          Requires 3 or 4 data sets. Either X, Y, Ydelta or X, Y, Ylower, Yupper. Ydelta is a change relative to the given Y value. The Ylower and Yupper values are the grid coordinates of the upper and lower values to indicate with error bars.
          - xyerrorbars: Draw both vertical and horizontal error bars around the points.
          Requires 4 or 6 data sets. Either X, Y, Xdelta, Ydelta or X, Y, Xlower, Xupper, Ylower, Yupper. Xdelta and Ydelta are relative to the given coordinates. Xlower, Xupper, Ylower, and Yupper are the grid coordinates of the upper and lower values to indicate with the error bars.
          - boxes: Draw a box from the X-axis to the Y-value given.
          Requires either 2 or 3 data sets. Either X, Y or X, Y, Xwidth. In the first case the boxes will be drawn next to eachother. In the latter case Xwidth indicates the horizontal width of the box for the given coordinate.
          - vector: Draws a vector field based on the given data.
          Requires 4 data sets, X, Y, Xdelta, and Ydelta. The ‘vector‘ style draws a vector from (X,Y) to (X+Xdelta,Y+Ydelta). It also draws a small arrowhead at the end of the vector. May not be fully supported by gnuplot.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="linewidth">
        <TITRE>line width</TITRE>
        <DOCUMENTATION>
          Line width (may not apply to all plot styles)
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="linetype">
        <TITRE>line type</TITRE>
        <DOCUMENTATION>
          Line type (may not apply to all plot styles)
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="pointsize">
        <TITRE>point size</TITRE>
        <DOCUMENTATION>
          Point size (may not apply to all plot styles)
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="pointtype">
        <TITRE>point type</TITRE>
        <DOCUMENTATION>
          Point type (may not apply to all plot styles)
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="limit">
        <TITRE>limit</TITRE>
        <DOCUMENTATION>
          Point to fill for filled curves
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="arrowhead">
        <TITRE>arrow head</TITRE>
        <DOCUMENTATION>
          For vector plots, controls where in the vector the arrow head(s) appear.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="arrowstyle">
        <TITRE>arrow style</TITRE>
        <DOCUMENTATION>
          For vector plots, controls the fill style of the arrow.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="arrowlength">
        <TITRE>arrow length</TITRE>
        <DOCUMENTATION>
          For vector plots, determines the distance between the vector line end and the tip of the arrow.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="arrowangle">
        <TITRE>arrow angle</TITRE>
        <DOCUMENTATION>
          For vector plots, determines the angle the arrow branches make with the vector line.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="arrowbackangle">
        <TITRE>arrow back angle</TITRE>
        <DOCUMENTATION>
          For vector plots, determines the angle the arrow lines that return to the main line from the branches make with the arrow branches.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="data">
      <TITRE>Curve data</TITRE>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="function">
      <TITRE>Curve function</TITRE>
      <DOCUMENTATION>
        Used to specify the curve to be plotted as a formula, instead of numerical data.
        The function must be a mathematical expression. Use the independent variable “x” for cartesian plots and “t” for polar plots. Implicit multiplication is not accepted by Gnuplot.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="key">
      <TITRE>Key</TITRE>
      <DOCUMENTATION>
        Causes a key to be drawn on the plot when it is generated. The key will contain an entry for each curve which has a name.
        The key is the color of the foreground of the plot, specified in the gnuplot tag.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="title">
        <TITRE>title</TITRE>
        <DOCUMENTATION>
          Title of key
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="box">
        <TITRE>box</TITRE>
        <DOCUMENTATION>
          Draw a box around the key?
        </DOCUMENTATION>
        <TITRE_VALEUR valeur="on">on</TITRE_VALEUR>
        <TITRE_VALEUR valeur="off">off</TITRE_VALEUR>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="pos">
        <TITRE>pos</TITRE>
        <DOCUMENTATION>
          Position of the key on the plot
        </DOCUMENTATION>
        <TITRE_VALEUR valeur="top left">top left</TITRE_VALEUR>
        <TITRE_VALEUR valeur="top right">top right</TITRE_VALEUR>
        <TITRE_VALEUR valeur="bottom left">bottom left</TITRE_VALEUR>
        <TITRE_VALEUR valeur="bottom right">bottom right</TITRE_VALEUR>
        <TITRE_VALEUR valeur="outside">outside</TITRE_VALEUR>
        <TITRE_VALEUR valeur="below">below</TITRE_VALEUR>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="label">
      <TITRE>Label</TITRE>
      <DOCUMENTATION>
        In a gnuplot environment, the label element allows the author to place text at any position on the plot. There may be many label elements on one plot and all the labels which fall within the plot will show. The color used will be to foreground color of the plot and the font will be the size specified for the plot, both of which are set in the gnuplot element.
        
        In a randomlabel environment, this is a label text or path to image.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="description">
        <TITRE>description</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="xpos">
        <TITRE>xpos</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="ypos">
        <TITRE>ypos</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="justify">
        <TITRE>justify</TITRE>
        <TITRE_VALEUR valeur="left">left</TITRE_VALEUR>
        <TITRE_VALEUR valeur="right">right</TITRE_VALEUR>
        <TITRE_VALEUR valeur="center">center</TITRE_VALEUR>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="rotate">
        <TITRE>rotate</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="tic">
      <TITRE>Tic</TITRE>
      <DOCUMENTATION>
        The <tic> tag allows users to specify exact Tic positions and labels for each axis.
        In this version we only support level 0 tics (major tic).
        Each tic has associated with it a position and a label $current_tics is a reference to the current tick description hash.
        We add elements to an array in that has: ticspecs whose elements are 'pos' - the tick position and 'label' - the tic label.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="location">
        <TITRE>location</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="xtics">
      <TITRE>Xtics</TITRE>
      <STRINGS_ATTRIBUT attribut="location">
        <TITRE>location of major tic marks</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="mirror">
        <TITRE>mirror tics on opposite axis?</TITRE>
        <DOCUMENTATION>
          If the location of tic marks is set to “border” this parameter determines if they are shown on both the top and bottom or right and left sides of the graph. The “mirror” tic marks are unlabelled.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="start">
        <TITRE>start major tics at</TITRE>
        <DOCUMENTATION>
          The point in graph coordinates which to start making major tics. This may be less than or greater than the lower limit for the axis.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="increment">
        <TITRE>place a major tic every</TITRE>
        <DOCUMENTATION>
          The span, in graph coordinates, between each major tic mark.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="end">
        <TITRE>stop major tics at</TITRE>
        <DOCUMENTATION>
          This may be less than or greater than the upper limit for the axis.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="minorfreq">
        <TITRE>number of minor tics per major tic mark</TITRE>
        <DOCUMENTATION>
          The number of subdivisions to make of the span between major tic marks. Using a value of “10” leads to 9 minor tic marks.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="rotate">
        <TITRE>rotate tic label by 90 degrees if on</TITRE>
        <DOCUMENTATION>
          For output devices that support it, this rotates the tic label by 90 degrees. This is most useful with large lables defined by the tic tag described below.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="ytics">
      <TITRE>Ytics</TITRE>
      <STRINGS_ATTRIBUT attribut="location">
        <TITRE>location of major tic marks</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="mirror">
        <TITRE>mirror tics on opposite axis?</TITRE>
        <DOCUMENTATION>
          If the location of tic marks is set to “border” this parameter determines if they are shown on both the top and bottom or right and left sides of the graph. The “mirror” tic marks are unlabelled.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="start">
        <TITRE>start major tics at</TITRE>
        <DOCUMENTATION>
          The point in graph coordinates which to start making major tics. This may be less than or greater than the lower limit for the axis.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="increment">
        <TITRE>place a major tic every</TITRE>
        <DOCUMENTATION>
          The span, in graph coordinates, between each major tic mark.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="end">
        <TITRE>stop major tics at</TITRE>
        <DOCUMENTATION>
          This may be less than or greater than the upper limit for the axis.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="minorfreq">
        <TITRE>number of minor tics per major tic mark</TITRE>
        <DOCUMENTATION>
          The number of subdivisions to make of the span between major tic marks. Using a value of “10” leads to 9 minor tic marks.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="rotate">
        <TITRE>rotate tic label by 90 degrees if on</TITRE>
        <DOCUMENTATION>
          For output devices that support it, this rotates the tic label by 90 degrees. This is most useful with large lables defined by the tic tag described below.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="xlabel">
      <TITRE>Plot x-label</TITRE>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="ylabel">
      <TITRE>Plot y-label</TITRE>
    </STRINGS_ELEMENT>
    
    <STRINGS_ELEMENT element="randomlabel">
      <TITRE>Randomly labeled image</TITRE>
      <DOCUMENTATION>
        This shows a specified image with images or text labels randomly assigned to a set of specific locations. Those locations may also have values assigned to them. A hash is generated that contains the mapping of labels to locations, labels to values, and locations to values.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="bgimg">
        <TITRE>bgimg</TITRE>
        <DOCUMENTATION>
          Either a fully qualified URL for an external image or a LON-CAPA resource. It supports relative references (../images/apicture.gif). The image must either be a GIF or JPEG.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="width">
        <TITRE>image width (pixels)</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="height">
        <TITRE>image height (pixels)</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="texwidth">
        <TITRE>texwidth</TITRE>
        <DOCUMENTATION>
          The width of the image in millimeters.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="bgimg">
      <TITRE>bg img</TITRE>
      <DOCUMENTATION>
        ???
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="labelgroup">
      <TITRE>Group of Labels</TITRE>
      <DOCUMENTATION>
        One is required, but multiple are allowed. This declares a group of locations and labels associated with them.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
        <DOCUMENTATION>
          This is the name of the group.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="type">
        <TITRE>type</TITRE>
        <DOCUMENTATION>
          the type of labels in this group
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="TeXsize">
        <TITRE>TeX font size</TITRE>
        <DOCUMENTATION>
          TeX font size
          Warning: as opposed to the TeXsize attribute in <h1>..<h6> <font> and <basefont>, this one requires a \ at the beginning of the values.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="location">
      <TITRE>Label Location</TITRE>
      <DOCUMENTATION>
        declares a location on the image that a label should appear at
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="x">
        <TITRE>x</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="y">
        <TITRE>y</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="value">
        <TITRE>value</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    
    <STRINGS_ELEMENT element="part">
      <TITRE>Problem Part</TITRE>
      <DOCUMENTATION>
        Problem Part
        
        Provides a logical division in a problem, with an indivisible group of questions and responses.
        Two parts can be graded as right or wrong independantly.
        A part also provides a granularity level for parameters (weight, tolerance, ...).
        It is possible to display parts one at a time.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>part ID</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="display">
        <TITRE>displayed part description</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="allow">
      <TITRE>Allow</TITRE>
      <DOCUMENTATION>File Dependencies</DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="src">
        <TITRE>src</TITRE>
        <DOCUMENTATION>
          Path to the file
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="parserlib">
      <TITRE>Import Tag Definitions</TITRE>
      <DOCUMENTATION>
        The enclosed filename contains definitions for new tags.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="scriptlib">
      <TITRE>Import Script Library</TITRE>
      <DOCUMENTATION>
        The enclosed filename contains Perl code to run in the safe space.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="parameter">
      <TITRE>Parameter</TITRE>
      <DOCUMENTATION>
        Parameter for a part.
        
        parameter is exactly the same as responseparam, but should appear outside of a response tag.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="type">
        <TITRE>type</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="description">
        <TITRE>description</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="default">
        <TITRE>default</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="display">
        <TITRE>display</TITRE>
        <DOCUMENTATION>
          Title displayed on the parameter setting screen.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="displaytitle">
      <TITRE>Display title</TITRE>
      <DOCUMENTATION>
        This will insert the title of the problem from the metadata of the problem. Only the first displaytitle in a problem will show the title; this allows clean usage of displaytitle in LON-CAPA style files.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="style">
        <TITRE>style</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="displayduedate">
      <TITRE>Display due date</TITRE>
      <DOCUMENTATION>
        This will insert the current due date if one is set in the document.
        It is generated to be inside a table of 1x1 elements.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="style">
        <TITRE>style</TITRE>
        <DOCUMENTATION>
          style=“plain” Makes the due date appear without any boxing. If the parameter value is other than “plain”, or if the style parameter is omitted, the due date will be displayed within a box.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="format">
        <TITRE>format</TITRE>
        <DOCUMENTATION>
          Allows you to control the format of the due date. This is an arbitrary string that can contain any of the following formatting items:
          
          %a Replaced by the abbreviated weekday name according to the current locale.
          %A Replaced by the full weekday name according to the current locale.
          %b The abbreviated month name according to the current locale.
          %B The full month name according to the current locale.
          %c The preferred date and time representation for the current locale (the default format string is just this).
          %C The century number as a two digit integer
          %d The day of the month as a decimal number. Leading zeroes are shown for single digit day numbers.
          %D Equivalent to %m/%d/%y
          %e Like %d but a leadnig zero is replaced by a space.
          %F Equivalent to %Y-%m-%d
          %G The four digit year number.
          %g The two digit year numbger.
          %H The hour as a two digit number in the range 00 thorugh 23.
          %I The hour as a two digit number in the range 00 through 12.
          %j The day your the year in the range 001 through 366.
          %k The hour (24 hour clock), single digits are preceded by a blank.
          %l Like %k but using a 12 hour clock.
          %m The month as a two digit decimal number in the range 01 through 12.
          %M The minute as a two digit decimal number in the range 00 through 59.
          %n A newline character.
          %p AM or PM depending on the time value.
          %P am or pm.
          %r The time in am or pm notation.
          %R Time in 24 hour notatinon (%H:%M). See also %T below.
          %s Number of seconds since midnight of January 1, 1970.
          %S The second as a decimal number int the range 00 through 59.
          %t A horizontal tab character.
          %T The time in 24 hour notation (%H:%M:%S).
          %u Day of the week as a decimal number with Monday as 1.
          %U The week number of the current year in the range 00 through 53. Week 1 is the week containing the first Sunday of the year.
          %V Same as %U but week 1 is the first week with at least 4 days, with Monday being the first day of a week.
          %w Day of the week as a decimal integer in the range 0 through 7, Sunday is 0.
          %W Week number of the current year in the range 00 through 53, where the first Monday of the year is the first day of week 01.
          %x The preferred date notation in the current locale without the time.
          %X The preferred time notation in the current locale without the date.
          %y The year as a decimal number without the century (range 00 through 99).
          %Y The year as a decimal number including the century.
          %% A % character.
          %+ Date and time in the form returned by the Unix date command. 
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="preduedate">
      <TITRE>Before Due Date Block</TITRE>
      <DOCUMENTATION>
        Everything inside is skipped if the problem is after the due date.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="postanswerdate">
      <TITRE>After Answer Date Block</TITRE>
      <DOCUMENTATION>
        Everything inside is skipped if the problem is before the answer date.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="solved">
      <TITRE>Block For After Solved</TITRE>
      <DOCUMENTATION>
        Everything inside is skipped if the problem is “not solved”.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="notsolved">
      <TITRE>Block For When Not Solved</TITRE>
      <DOCUMENTATION>
        Everything inside is skipped if the problem is “solved”.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    
    <STRINGS_ELEMENT element="import">
      <TITRE>Import a File</TITRE>
      <DOCUMENTATION>
        This causes the parse to read in the file named in the body of the tag and parse it as if the entire text of the file had existed at the location of the tag.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="importmode">
        <TITRE>import as</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="block">
      <TITRE>Conditional Block</TITRE>
      <DOCUMENTATION>
        This has a required argument condition that is evaluated. If the condition is true, everything inside the tag is evaluated; otherwise, everything inside the block tag is skipped.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="condition">
        <TITRE>test condition</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="while">
      <TITRE>While Loop Block</TITRE>
      <DOCUMENTATION>
        This implements a while loop. The required attribute condition is a Perl scriptlet that when evaluated results in a true or false value. If true, the entirety of the text between the whiles is parsed. The condition is tested again, etc. If false, it goes to the next tag.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="condition">
        <TITRE>test condition</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="tex">
      <TITRE>Print Only Block (LaTeX)</TITRE>
      <DOCUMENTATION>
        Print Only Block (in LaTeX).
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="print">
      <TITRE>Print Only Block</TITRE>
      <DOCUMENTATION>
        Print Only Block (using HTML instead of LaTeX).
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="web">
      <TITRE>Web Only Block</TITRE>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="standalone">
      <TITRE>Standalone Block</TITRE>
      <DOCUMENTATION>
        Everything in between the start and end tag is shown only on the web and only if the resource is not part of a course.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="problemtype">
      <TITRE>Problem Type Block</TITRE>
      <DOCUMENTATION>
        Allows you to show or hide output based on what the problem-type parameter is set to in the course.
        Will only show the output text when the problem is set to the type of exam or survey in the course.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="mode">
        <TITRE>mode</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="for">
        <TITRE>for</TITRE>
        <DOCUMENTATION>
          When used as type(s)
          
          Comma-separated list of values among:
          exam, survey, surveycred, anonsurvey, anonsurveycred, problem, practice, randomizetry
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="randomlist">
      <TITRE>Randomly Parsed Block</TITRE>
      <DOCUMENTATION>
        The enclosed tags are parsed in a stable random order. The optional attribute show=“N” restricts the number of tags inside that are actually parsed to no more than N. N can equal the total tags inside. The randomlist tag can be used to randomize problem parts by wrapping the <part> tags with a randomlist tag. Note that when randomlist wraps <part> tags, that all students will work all parts only if show=“N” where N is the total number of parts wrapped. When N is less than the total number of parts wrapped, there will be gaps in the assessment chart, and also in the table of submissions for each student, corresponding to those parts which are never available to that particular student.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="show">
        <TITRE>maximum tags to show</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="languageblock">
      <TITRE>Language Block</TITRE>
      <DOCUMENTATION>
        This declares the intent to provide content that can be rendered in the set of languages in the include specification but not in the exclude specification. If a currently preferred language is in the include list the content in the <languageblock>...</languageblock> is rendered If the currently preferred language is in the exclude list, the content in the <languageblock>..</languageblock is not rendered.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="include">
        <TITRE>include</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="exclude">
        <TITRE>exclude</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="translated">
      <TITRE>Translated Block</TITRE>
      <DOCUMENTATION>
        <translated> starts a block of a resource that has multiple translations.
        See the <lang> tag as well.
        When </translated> is encountered if there is a translation for the currently preferred language, that is rendered inthe web/tex/webgrade targets. Otherwise, the default text is rendered.
        Note that <lang> is only registered for the duration of the <translated>...</translated> block.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="lang">
      <TITRE>Language</TITRE>
      <DOCUMENTATION>
        Specifies that the block contained within it is a translation for a specific language specified by the 'which' attribute. The 'other' attribute can be used by itself or in conjunction with which to specify this tag _may_ be used as a translation for some list of languages. e.g.:
        <lang which='senisoUS' other='senisoCA,senisoAU,seniso'>
        specifying that the block provides a translation for US (primary) Canadian, Australian and UK English.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="which">
        <TITRE>which</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="other">
        <TITRE>other</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="window">
      <TITRE>Text In Separate Window</TITRE>
      <DOCUMENTATION>
        This creates a link that when clicked shows the intervening information in a pop-up window. By default the window will be 500 pixels wide and 200 pixels tall, and the link text will be a superscript * (so as to look like a footnote). These can be changed using the attributes.
        When printing, the included text will get turned into a real footnote.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="linktext">
        <TITRE>text of link</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="height">
        <TITRE>height</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="width">
        <TITRE>width</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="printtext">
        <TITRE>printed text</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="windowlink">
      <TITRE>Window Link</TITRE>
      <DOCUMENTATION>
        This creates a link to a resource that comes up in a pop-up window.
        The link will be the intervening information between the start and the end tag.
        By default the window will be 500 pixels wide and 200 pixels tall.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="href">
        <TITRE>href</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="height">
        <TITRE>height</TITRE>
        <DOCUMENTATION>
          starting height of the popup window
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="width">
        <TITRE>width</TITRE>
        <DOCUMENTATION>
          starting width of the popup window
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="togglebox">
      <TITRE>Toggle Box</TITRE>
      <DOCUMENTATION>
        This creates a toggling box that can be clicked open and close.
        When printing, the included text will be rendered in a visible box.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="heading">
        <TITRE>heading</TITRE>
        <DOCUMENTATION>
          heading text of the box, by default no heading
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="headerbg">
        <TITRE>headerbg</TITRE>
        <DOCUMENTATION>
          background color of the header, by default white
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="showtext">
        <TITRE>show text</TITRE>
        <DOCUMENTATION>
          the text that appears to make the box visible, by default the translation of ’show’
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="hidetext">
        <TITRE>hide text</TITRE>
        <DOCUMENTATION>
          the text that appears to hide the box again, by default the translation of ’hide’
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="instructorcomment">
      <TITRE>Instructor Comment</TITRE>
      <DOCUMENTATION>
        Comment that is hidden if form.instructor_comments='hide'.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="comment">
      <TITRE>Comment</TITRE>
      <DOCUMENTATION>
        Allows one to comment out sections of code in a balanced manner, or to provide a comment description of how a problem works.
        The content is ignored.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="organicstructure">
      <TITRE>Organic Structure</TITRE>
      <STRINGS_ATTRIBUT attribut="width">
        <TITRE>width (pixels)</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="texwidth">
        <TITRE>TeXwidth (mm)</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="molecule">
        <TITRE>molecule</TITRE>
        <DOCUMENTATION>
          JME string
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="options">
        <TITRE>options</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="drawimage">
      <TITRE>Draw Image</TITRE>
      <DOCUMENTATION>
        Draws an image with the specified objects using pixel coordinates (text, line, rectangle, arc, fill, polygon, image).
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="width">
        <TITRE>width</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="height">
        <TITRE>height</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="bgcolor">
        <TITRE>bgcolor</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="line">
      <TITRE>Line</TITRE>
      <STRINGS_ATTRIBUT attribut="x1">
        <TITRE>x1</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="y1">
        <TITRE>y1</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="x2">
        <TITRE>x2</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="y2">
        <TITRE>y2</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="color">
        <TITRE>color</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="thickness">
        <TITRE>thickness</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="arc">
      <TITRE>Arc</TITRE>
      <STRINGS_ATTRIBUT attribut="x">
        <TITRE>x</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="y">
        <TITRE>y</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="width">
        <TITRE>width</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="height">
        <TITRE>height</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="start">
        <TITRE>start</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="end">
        <TITRE>end</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="color">
        <TITRE>color</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="thickness">
        <TITRE>thickness</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="filled">
        <TITRE>filled</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="fill">
      <TITRE>Fill</TITRE>
      <STRINGS_ATTRIBUT attribut="x">
        <TITRE>x</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="y">
        <TITRE>y</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="color">
        <TITRE>color</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="point">
      <TITRE>Point</TITRE>
      <STRINGS_ATTRIBUT attribut="x">
        <TITRE>x</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="y">
        <TITRE>y</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    
    <STRINGS_ELEMENT element="display">
      <TITRE>Display Script Result Block</TITRE>
      <DOCUMENTATION>
        The intervening Perl script is evaluated in the safe space and the return value of the script replaces the entire tag.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="m">
      <TITRE>LaTeX math</TITRE>
      <DOCUMENTATION>
        The inside text is LaTeX, and is converted to HTML (or MathML) on the fly.
        This element is normally used for math, and the text should start and end with either $ or $$.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="display">
        <TITRE>display</TITRE>
        <DOCUMENTATION>
          Option to force the math rendering for this element.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="eval">
        <TITRE>eval</TITRE>
        <DOCUMENTATION>
          Perl variables inside the element will be evaluated if this attribute value is "on".
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="lm">
      <TITRE>LON-CAPA inline math</TITRE>
      <DOCUMENTATION>
        Inline math with the LON-CAPA syntax (use <m> for LaTeX math).
        Perl variables are evaluated.
        The expression is interpreted with implicit operators (for multiplication or units).
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="mode">
        <TITRE>mode</TITRE>
        <DOCUMENTATION>
          In symbols mode, names are interpreted as constants or variables.
          In units mode, names are interpreted as constants or units.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="num">
      <TITRE>Num</TITRE>
      <DOCUMENTATION>
        Typesets a number formatted in scientific notation, fixed point, fixed point with commas, fixed point with commas and dollar sign, or in significant digits.
        
        <num format="2E">31454678</num> results in 3.15 x 10^7
        <num format="2f">31454678</num> results in 31454678.00
        <num format="4g">31454678</num> results in 3.145 x 10^7
        <num format="4g">314.54678</num> results in 314.5
        <num format=",2f">31454678</num> results in 31,454,678.00
        <num format="$2f">31454678</num> results in $31,454,678.00
        <num format="2s">31454678</num> results in 31000000
        <num format=",2s">31454678</num> results in 31,000,000
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="format">
        <TITRE>format</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="algebra">
      <TITRE>Algebra</TITRE>
      <DOCUMENTATION>
        Typesets algebraic expressions.
        Expressions are displayed using the math expression display mechanism defined in the user’s preferences. The default is tth.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="style">
        <TITRE>style</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="chem">
      <TITRE>Chem</TITRE>
      <DOCUMENTATION>
        Typesets chemical equation
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="parse">
      <TITRE>Parse</TITRE>
      <DOCUMENTATION>
        to display the parsed view of a variable’s contents
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="displayweight">
      <TITRE>Display weight</TITRE>
      <DOCUMENTATION>
        Displays the number of points awarded for this problem or problem part.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="displaystudentphoto">
      <TITRE>Display student photo</TITRE>
      <STRINGS_ATTRIBUT attribut="width">
        <TITRE>width</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="height">
        <TITRE>height</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="align">
        <TITRE>align</TITRE>
        <DOCUMENTATION>
          note: this attribute is not supported in HTML5, css should be used instead !
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    
    <STRINGS_ELEMENT element="Task">
      <TITRE>Task</TITRE>
      <DOCUMENTATION>Root for .task (bridge task) documents</DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="OptionalRequired">
        <TITRE>OptionalRequired</TITRE>
        <DOCUMENTATION>
          Required number of passed optional elements to pass the Task
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="IntroParagraph">
      <TITRE>IntroParagraph</TITRE>
      <DOCUMENTATION>Introductory Information</DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="ClosingParagraph">
      <TITRE>ClosingParagraph</TITRE>
      <DOCUMENTATION>Closing Information</DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="Question">
      <TITRE>Question</TITRE>
      <DOCUMENTATION>Question</DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="Mandatory">
        <TITRE>Mandatory</TITRE>
        <DOCUMENTATION>
          Passing is Mandatory
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="OptionalRequired">
        <TITRE>OptionalRequired</TITRE>
        <DOCUMENTATION>
          Required number of passed optional elements to pass
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="QuestionText">
      <TITRE>QuestionText</TITRE>
      <DOCUMENTATION>Question Information</DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="Setup">
      <TITRE>Setup</TITRE>
      <DOCUMENTATION>Setup....</DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="Instance">
      <TITRE>Instance</TITRE>
      <DOCUMENTATION>Specific Question Instance</DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="Disabled">
        <TITRE>Disabled</TITRE>
        <DOCUMENTATION>
          Instance is Disabled
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="OptionalRequired">
        <TITRE>OptionalRequired</TITRE>
        <DOCUMENTATION>
          Required number of passed optional elements to pass the Instance
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="InstanceText">
      <TITRE>InstanceText</TITRE>
      <DOCUMENTATION>Information for the Instance</DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="Criteria">
      <TITRE>Criteria</TITRE>
      <DOCUMENTATION>Question Criteria</DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="Mandatory">
        <TITRE>Mandatory</TITRE>
        <DOCUMENTATION>
          Passing is Mandatory
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="CriteriaText">
      <TITRE>CriteriaText</TITRE>
      <DOCUMENTATION>Criteria Information</DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="GraderNote">
      <TITRE>GraderNote</TITRE>
      <DOCUMENTATION>Text to display to Grader</DOCUMENTATION>
    </STRINGS_ELEMENT>
    
    <STRINGS_ELEMENT element="html">
      <TITRE>HTML</TITRE>
      <DOCUMENTATION>
        The HTML root element (<html>) represents the root of an HTML document.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="head">
      <TITRE>HTML Head</TITRE>
      <DOCUMENTATION>
        The HTML Head Element (<head>) provides general information (metadata) about the document, including its title and links to or definitions of scripts and style sheets.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="profile">
        <TITRE>profile</TITRE>
        <DOCUMENTATION>
          The URIs of one or more metadata profiles, separated by white space.
          
          This attribute is obsolete in HTML5.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="base">
      <TITRE>Base URI</TITRE>
      <DOCUMENTATION>
        Document base URI
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="href">
        <TITRE>href</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="meta">
      <TITRE>Meta</TITRE>
      <DOCUMENTATION>
        Note: This might be an HTML meta or a LON-CAPA meta.
        
        The HTML <meta> Element represents any metadata information that cannot be represented by one of the other meta-related elements (<base>, <link>, <script>, <style> or <title>).
        According to the attributes set, the kind of metadata can be one of the following:
        - if the name is set, a document-level metadata, applying to the whole page;
        - if the http-equiv is set, a pragma directive, i.e. information normally given from the webserver on how the webpage should be served;
        - if the charset is set, a charset declaration, i.e. the charset used for the serialized-form of the webpage; HTML5
        - if the itemprop is set, a user-defined metadata, transparent for the user-agent as the semantics of the metadata is user-specific.
        
        Recognized names for LON-CAPA:
        abstract, author, authorspace, avetries, avetries_list, clear, comefrom, comefrom_list, copyright, correct, count, course, course_list, courserestricted, creationdate, dependencies, depth, difficulty, difficulty_list, disc, disc_list, domain, end, field, firstname, generation, goto, goto_list, groupname, helpful, highestgradelevel, hostname, id, keynum, keywords, language, lastname, lastrevisiondate, lowestgradelevel, middlename, mime, modifyinguser, notes, owner, permanentemail, scope, sequsage, sequsage_list, standards, start, stdno, stdno_list, subject, technical, title, url, username, value, version.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
        <DOCUMENTATION>
          This attribute defines the name of a document-level metadata. It should not be set if one of the attributes itemprop, http-equiv or charset is also set.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="content">
        <TITRE>content</TITRE>
        <DOCUMENTATION>
          This attribute gives the value associated with the http-equiv or name attribute, depending of the context.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="http-equiv">
        <TITRE>http-equiv</TITRE>
        <DOCUMENTATION>
          This enumerated attribute defines the pragma that can alter servers and user-agents behavior. The value of the pragma is defined using the content and can be one of the following: 
          - content-language (obsolete)
          - content-type (obsolete)
          - default-style
          - refresh
          - set-cookie (obsolete)
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="scheme">
        <TITRE>scheme</TITRE>
        <DOCUMENTATION>
          This attribute defines the scheme in which the metadata is described. A scheme is a context leading to the correct interpretations of the content value, like a format.
          
          Notes: Do not use this attribute as it is obsolete. There is no replacement for it as there was no real usage for it. Omit it altogether.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="link">
      <TITRE>Link</TITRE>
      <DOCUMENTATION>
        The HTML Link Element (<link>) specifies relationships between the current document and external resource. Possible uses for this element include defining a relational framework for navigation. This Element is most used to link to style sheets.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="charset">
        <TITRE>charset</TITRE>
        <DOCUMENTATION>
          This attribute defines the character encoding of the linked resource. The value is a space- and/or comma-delimited list of character sets as defined in RFC 2045. The default value is ISO-8859-1.
          
          Usage note: This attribute is obsolete in HTML5 and must not be used by authors. To achieve its effect, use the Content-Type: HTTP header on the linked resource.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="href">
        <TITRE>href</TITRE>
        <DOCUMENTATION>
          This attribute specifies the URL of the linked resource. A URL might be absolute or relative.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="hreflang">
        <TITRE>hreflang</TITRE>
        <DOCUMENTATION>
          This attribute indicates the language of the linked resource. It is purely advisory. Allowed values are determined by BCP47 for HTML5 and by RFC1766 for HTML 4. Use this attribute only if the href attribute is present.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="type">
        <TITRE>type</TITRE>
        <DOCUMENTATION>
          This attribute is used to define the type of the content linked to. The value of the attribute should be a MIME type such as text/html, text/css, and so on. The common use of this attribute is to define the type of style sheet linked and the most common current value is text/css, which indicates a Cascading Style Sheet format.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="rel">
        <TITRE>rel</TITRE>
        <DOCUMENTATION>
          This attribute names a relationship of the linked document to the current document. The attribute must be a space-separated list of the link types values. The most common use of this attribute is to specify a link to an external style sheet: the rel attribute is set to stylesheet, and the href attribute is set to the URL of an external style sheet to format the page. WebTV also supports the use of the value next for rel to preload the next page in a document series.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="rev">
        <TITRE>rev</TITRE>
        <DOCUMENTATION>
          The value of this attribute shows the relationship of the current document to the linked document, as defined by the href attribute. The attribute thus defines the reverse relationship compared to the value of the rel attribute. Link types values for the attribute are similar to the possible values for rel.
          
          Usage note: This attribute is obsolete in HTML5. Do not use it. To achieve its effect, use the rel attribute with the opposite link types values, e.g. made should be replaced by author. Also this attribute doesn't mean revision and must not be used with a version number, which is unfortunately the case on numerous sites.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="media">
        <TITRE>media</TITRE>
        <DOCUMENTATION>
          This attribute specifies the media which the linked resource applies to. Its value must be a media query. This attribute is mainly useful when linking to external stylesheets by allowing the user agent to pick the best adapted one for the device it runs on.
          
          Usage note:
          
          - In HTML 4, this can only be a simple white-space-separated list of media description literals, i.e., media types and groups, where defined and allowed as values for this attribute, such as print, screen, aural, braille. HTML5 extended this to any kind of media queries, which are a superset of the allowed values of HTML 4.
          
          - Browsers not supporting the CSS3 Media Queries won't necessarily recognize the adequate link; do not forget to set fallback links, the restricted set of media queries defined in HTML 4.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="style">
      <TITRE>Style</TITRE>
      <DOCUMENTATION>
        The HTML <style> element contains style information for a document, or a part of document. The specific style information is contained inside of this element, usually in the CSS.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="type">
        <TITRE>type</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="media">
        <TITRE>media</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="title">
        <TITRE>title</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="space">
        <TITRE>xml:space</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="script">
      <TITRE>Script</TITRE>
      <DOCUMENTATION>
        Perl Script Block or Javascript
        
        If the attribute type is set to “loncapa/perl” the enclosed data is a Perl script which is evaluated inside the Perl safe space. The return value of the script is ignored.
        
        The HTML <script> element is used to embed or reference an executable script within an HTML or XHTML document.
        
        Scripts without async or defer attributes, as well as inline scripts, are fetched and executed immediately, before the browser continues to parse the page.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="src">
        <TITRE>src</TITRE>
        <DOCUMENTATION>
          This attribute specifies the URI of an external script; this can be used as an alternative to embedding a script directly within a document. script elements with an src attribute specified should not have a script embedded within its tags.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="type">
        <TITRE>type</TITRE>
        <DOCUMENTATION>
          This attribute identifies the scripting language of code embedded within a script element or referenced via the element’s src attribute. This is specified as a MIME type; examples of supported MIME types include text/javascript, text/ecmascript, application/javascript, and application/ecmascript.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="language">
        <TITRE>language</TITRE>
        <DOCUMENTATION>
          Like the type attribute, this attribute identifies the scripting language in use. Unlike the type attribute, however, this attribute’s possible values were never standardized. The type attribute should be used instead.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="defer">
        <TITRE>defer</TITRE>
        <DOCUMENTATION>
          This Boolean attribute is set to indicate to a browser that the script is meant to be executed after the document has been parsed. Since this feature hasn't yet been implemented by all other major browsers, authors should not assume that the script’s execution will actually be deferred. The defer attribute shouldn't be used on scripts that don't have the src attribute. Since Gecko 1.9.2, the defer attribute is ignored on scripts that don't have the src attribute. However, in Gecko 1.9.1 even inline scripts are deferred if the defer attribute is set.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="async">
        <TITRE>async</TITRE>
        <DOCUMENTATION>
          HTML5 only.
          Set this Boolean attribute to indicate that the browser should, if possible, execute the script asynchronously. It has no effect on inline scripts (i.e., scripts that don't have the src attribute).
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="noscript">
      <TITRE>Block when no script</TITRE>
      <DOCUMENTATION>
        Alternate content container for non script-based rendering.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="body">
      <TITRE>HTML Body</TITRE>
      <DOCUMENTATION>
        Contains only the attributes that will be used in the future HTML document body. It is an empty element.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="onload">
        <TITRE>onload</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="onunload">
        <TITRE>onunload</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="section">
      <TITRE>Section</TITRE>
      <DOCUMENTATION>
        The HTML Section Element (<section>) represents a generic section of a document, i.e., a thematic grouping of content, typically with a heading. Each <section> should be identified, typically by including a heading (h1-h6 element) as a child of the <section> element.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="class">
        <TITRE>class</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="header">
      <TITRE>Header</TITRE>
      <DOCUMENTATION>
        The HTML <header> Element represents a group of introductory or navigational aids. It may contain some heading elements but also other elements like a logo, wrapped section's header, a search form, and so on.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="footer">
      <TITRE>Footer</TITRE>
      <DOCUMENTATION>
        The HTML <footer> Element represents a footer for its nearest sectioning content or sectioning root element (i.e, its nearest parent <article>, <aside>, <nav>, <section>, <blockquote>, <body>, <details>, <fieldset>, <figure>, <td>). A footer typically contains information about the author of the section, copyright data or links to related documents.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="aside">
      <TITRE>Aside</TITRE>
      <DOCUMENTATION>
        The HTML <aside> element represents a section of the page with content connected tangentially to the rest, which could be considered separate from that content. These sections are often represented as sidebars or inserts. They often contain the definitions on the sidebars, such as definitions from the glossary; there may also be other types of information, such as related advertisements; the biography of the author; web applications; profile information or related links on the blog.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="h1">
      <TITRE>Lvl 1 Title</TITRE>
      <DOCUMENTATION>
        A heading element briefly describes the topic of the section it introduces. Heading information may be used by user agents, for example, to construct a table of contents for a document automatically.
        
        Inside HTML5 sections, all the heading elements can be h1 (they don't need to be h1, h2, ...). Web browsers determine the level of the heading based on the depth in the section tree.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="TeXsize">
        <TITRE>TeXsize</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="h2">
      <TITRE>Lvl 2 Title</TITRE>
      <DOCUMENTATION>
        A heading element briefly describes the topic of the section it introduces. Heading information may be used by user agents, for example, to construct a table of contents for a document automatically.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="TeXsize">
        <TITRE>TeXsize</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="h3">
      <TITRE>Lvl 3 Title</TITRE>
      <DOCUMENTATION>
        A heading element briefly describes the topic of the section it introduces. Heading information may be used by user agents, for example, to construct a table of contents for a document automatically.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="TeXsize">
        <TITRE>TeXsize</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="h4">
      <TITRE>Lvl 4 Title</TITRE>
      <DOCUMENTATION>
        A heading element briefly describes the topic of the section it introduces. Heading information may be used by user agents, for example, to construct a table of contents for a document automatically.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="TeXsize">
        <TITRE>TeXsize</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="h5">
      <TITRE>Lvl 5 Title</TITRE>
      <DOCUMENTATION>
        A heading element briefly describes the topic of the section it introduces. Heading information may be used by user agents, for example, to construct a table of contents for a document automatically.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="TeXsize">
        <TITRE>TeXsize</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="h6">
      <TITRE>Lvl 6 Title</TITRE>
      <DOCUMENTATION>
        A heading element briefly describes the topic of the section it introduces. Heading information may be used by user agents, for example, to construct a table of contents for a document automatically.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="TeXsize">
        <TITRE>TeXsize</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="div">
      <TITRE>Division</TITRE>
      <DOCUMENTATION>
        HTML Division
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="class">
        <TITRE>class</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="p">
      <TITRE>Paragraph</TITRE>
      <DOCUMENTATION>
        The HTML <p> element (or HTML Paragraph Element) represents a paragraph of text. Paragraphs are block-level elements.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="ul">
      <TITRE>Unordered List</TITRE>
      <DOCUMENTATION>
        Unordered list
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="ol">
      <TITRE>Ordered List</TITRE>
      <DOCUMENTATION>
        Ordered list
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="li">
      <TITRE>Item</TITRE>
      <DOCUMENTATION>
        List item
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="dl">
      <TITRE>Definition List</TITRE>
      <DOCUMENTATION>
        The HTML <dl> Element (or HTML Description List Element) encloses a list of pairs of terms and descriptions. Common uses for this element are to implement a glossary or to display metadata (a list of key-value pairs).
        
        Prior to HTML5, <dl> was known as a Definition List.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="dt">
      <TITRE>Term</TITRE>
      <DOCUMENTATION>
        The HTML <dt> element (or HTML Definition Term Element) identifies a term in a definition list. This element can occur only as a child element of a <dl>. It is usually followed by a <dd> element; however, multiple <dt> elements in a row indicate several terms that are all defined by the immediate next <dd> element.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="dd">
      <TITRE>Description</TITRE>
      <DOCUMENTATION>
        The HTML <dd> Element (or HTML Description Element) indicates the description of a term in a description list (<dl>) element. This element can occur only as a child element of a definition list and it must follow a <dt> element.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="table">
      <TITRE>Table</TITRE>
      <DOCUMENTATION>
        The HTML Table Element (<table>) represents data in two dimensions or more.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="style">
        <TITRE>style</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="border">
        <TITRE>border</TITRE>
        <DOCUMENTATION>
          This integer attribute defines, in pixels, the size of the frame surrounding the table. If set to 0, it implies that the frame attribute is set to void.
          Usage note: Do not use this attribute, as it has been deprecated: the <table> element should be styled using CSS. To give a similar effect than the border attribute, the CSS properties border, border-color, border-width and border-style should be used.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="cellpadding">
        <TITRE>cellpadding</TITRE>
        <DOCUMENTATION>
          This attribute defines the space between the content of a cell and the border, displayed or not, of it. If it is a pixel length, this pixel-sized space will be applied on all four sides; if it is a percentage length, the content will be centered and the total vertical space (top and bottom) will represent this percentage. The same is true for the total horizontal space (left and right).
          Usage note: Do not use this attribute, as it has been deprecated: the <table> element should be styled using CSS. To give a similar effect than the border attribute, use the CSS property border-collapse with the value collapse on the <table> element itself, and the property padding on the <td>.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="cellspacing">
        <TITRE>cellspacing</TITRE>
        <DOCUMENTATION>
          This attribute defines the size, in percentage or in pixels, of the space between two cells (both horizontally and vertically), between the top of the table and the cells of the first row, the left of the table and the first column, the right of the table and the last column and the bottom of the table and the last row.
          Usage note: Do not use this attribute, as it has been deprecated: the <table> element should be styled using CSS. To give a similar effect than the border attribute, use the CSS property border-collapse with the value collapse on the <table> element itself, and the property margin on the <td> element.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="TeXwidth">
        <TITRE>TeX width</TITRE>
        <DOCUMENTATION>
          Width of the table in %
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="TeXtheme">
        <TITRE>TeX theme</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="caption">
      <TITRE>Table Caption</TITRE>
      <DOCUMENTATION>
        The HTML <caption> Element (or HTML Table Caption Element) represents the title of a table. Though it is always the first descendant of a <table>, its styling, using CSS, may place it elsewhere, relative to the table.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="tr">
      <TITRE>Table Row</TITRE>
      <DOCUMENTATION>
        Table row
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="td">
      <TITRE>Table Cell</TITRE>
      <DOCUMENTATION>
        Table cell
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="colspan">
        <TITRE>colspan</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="rowspan">
        <TITRE>rowspan</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="align">
        <TITRE>align</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="TeXwidth">
        <TITRE>TeX width</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="th">
      <TITRE>Table Header Cell</TITRE>
      <DOCUMENTATION>
        Table header cell
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="colspan">
        <TITRE>colspan</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="rowspan">
        <TITRE>rowspan</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="align">
        <TITRE>align</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="TeXwidth">
        <TITRE>TeX width</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="span">
      <TITRE>Span</TITRE>
      <DOCUMENTATION>
        Inline style
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="class">
        <TITRE>class</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="style">
        <TITRE>style</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="a">
      <TITRE>Link/Anchor</TITRE>
      <DOCUMENTATION>
        The HTML <a> Element (or the HTML Anchor Element) defines a hyperlink, the named target destination for a hyperlink, or both.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="href">
        <TITRE>href</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="strong">
      <TITRE>Strong</TITRE>
      <DOCUMENTATION>
        Strong emphasis
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="em">
      <TITRE>Emphasis</TITRE>
      <DOCUMENTATION>
        Emphasis
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="b">
      <TITRE>Bold</TITRE>
      <DOCUMENTATION>
        The HTML <b> Element represents a span of text stylistically different from normal text, without conveying any special importance or relevance. It is typically used for keywords in a summary, product names in a review, or other spans of text whose typical presentation would be boldfaced. Another example of its use is to mark the lead sentence of each paragraph of an article.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="i">
      <TITRE>Italic</TITRE>
      <DOCUMENTATION>
        The HTML <i> Element represents a range of text that is set off from the normal text for some reason, for example, technical terms, foreign language phrases, or fictional character thoughts. It is typically displayed in italic type.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="sup">
      <TITRE>Superscript</TITRE>
      <DOCUMENTATION>
        The HTML Superscript Element (<sup>) defines a span of text that should be displayed, for typographic reasons, higher, and often smaller, than the main span of text.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="sub">
      <TITRE>Subscript</TITRE>
      <DOCUMENTATION>
        The HTML Subscript Element (<sub>) defines a span of text that should be displayed, for typographic reasons, lower, and often smaller, than the main span of text.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="pre">
      <TITRE>Preformatted</TITRE>
      <DOCUMENTATION>
        The HTML <pre> Element (or HTML Preformatted Text) represents preformatted text. Text within this element is typically displayed in a non-proportional font exactly as it is laid out in the file. Whitespaces inside this element are displayed as typed.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="code">
      <TITRE>Code</TITRE>
      <DOCUMENTATION>
        The HTML <code> Element represents a fragment of computer code. By default, it is displayed in the browser's default monospace font.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="kbd">
      <TITRE>Keyboard</TITRE>
      <DOCUMENTATION>
        The HTML <kbd> Element (or HTML Keyboard Input Element) represents user input and produces an inline element displayed in the browser's default monotype font.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="samp">
      <TITRE>Sample</TITRE>
      <DOCUMENTATION>
        The HTML <samp> element is an element intended to identify sample output from a computer program. It is usually displayed in the browser's default monotype font.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="cite">
      <TITRE>Citation</TITRE>
      <DOCUMENTATION>
        The HTML <cite> Element (or HTML Citation Element) represents a reference to a creative work. It must include the title of a work, the name of the author, or a URL reference, which may be in an abbreviated form according to the conventions used for the addition of citation metadata.
        
        Usage Notes:
        
        A creative work may include a book, a paper, an essay, a poem, a score, a song, a script, a film, a TV show, a game, a sculpture, a painting, a theater production, a play, an opera, a musical, an exhibition, a legal case report, a computer program, , a web site, a web page, a blog post or comment, a forum post or comment, a tweet, a written or oral statement, etc.
        Use the cite attribute on a <blockquote> or <q> element to reference an online resource for a source.
        
        
        Style note:
        
        To avoid the default italic style from being used for the <cite> element use the CSS font-style property.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="q">
      <TITRE>Quote</TITRE>
      <DOCUMENTATION>
        The HTML <q> Element (or HTML Quote Element) indicates that the enclosed text is a short inline quotation. This element is intended for short quotations that don't require paragraph breaks; for long quotations use <blockquote> element.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="cite">
        <TITRE>cite</TITRE>
        <DOCUMENTATION>
          The value of this attribute is a URL that designates a source document or message for the information quoted. This attribute is intended to point to information explaining the context or the reference for the quote.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="tt">
      <TITRE>Teletype</TITRE>
      <DOCUMENTATION>
        This feature is obsolete. Although it may still work in some browsers, its use is discouraged since it could be removed at any time. Try to avoid using it.
        The HTML Teletype Text Element (<tt>) produces an inline element displayed in the browser's default monotype font. This element was intended to style text as it would display on a fixed width display, such as a teletype. It probably is more common to display fixed width type using the <code> element.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="ins">
      <TITRE>Inserted</TITRE>
      <DOCUMENTATION>
        The HTML <ins> Element (or HTML Inserted Text) HTML represents a range of text that has been added to a document.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="del">
      <TITRE>Deleted</TITRE>
      <DOCUMENTATION>
        The HTML <del> element (or HTML Deleted Text Element) represents a range of text that has been deleted from a document. This element is often (but need not be) rendered with strike-through text.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="var">
      <TITRE>Var</TITRE>
      <DOCUMENTATION>
        The HTML Variable Element (<var>) represents a variable in a mathematical expression or a programming context.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="small">
      <TITRE>Small</TITRE>
      <DOCUMENTATION>
        The HTML Small Element (<small>) makes the text font size one size smaller (for example, from large to medium, or from small to x-small) down to the browser's minimum font size. In HTML5, this element is repurposed to represent side-comments and small print, including copyright and legal text, independent of its styled presentation.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="big">
      <TITRE>Big</TITRE>
      <DOCUMENTATION>
        This feature is obsolete. Although it may still work in some browsers, its use is discouraged since it could be removed at any time. Try to avoid using it.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="br">
      <TITRE>Line Break</TITRE>
      <DOCUMENTATION>
        The HTML <br> Element (or HTML Line Break Element) produces a line break in text (carriage-return). It is useful for writing a poem or an address, where the division of lines is significant.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="hr">
      <TITRE>Horizontal Rule</TITRE>
      <DOCUMENTATION>
        The HTML <hr> element represents a thematic break between paragraph-level elements (for example, a change of scene in a story, or a shift of topic with a section). In previous versions of HTML, it represented a horizontal rule. It may still be displayed as a horizontal rule in visual browsers, but is now defined in semantic terms, rather than presentational terms.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="address">
      <TITRE>Address</TITRE>
      <DOCUMENTATION>
        The HTML <address> Element may be used by authors to supply contact information for its nearest <article> or <body> ancestor; in the latter case, it applies to the whole document.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="blockquote">
      <TITRE>Quotation Block</TITRE>
      <DOCUMENTATION>
        The HTML <blockquote> Element (or HTML Block Quotation Element) indicates that the enclosed text is an extended quotation. Usually, this is rendered visually by indentation (see Notes for how to change it). A URL for the source of the quotation may be given using the cite attribute, while a text representation of the source can be given using the <cite> element.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="cite">
        <TITRE>cite</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="img">
      <TITRE>Image</TITRE>
      <DOCUMENTATION>
        The HTML <img> Element (or HTML Image Element) represents an image of the document.
        
        Usage note:
        Browsers do not always display the image referenced by the element. This is the case for non-graphical browsers (including those used by people with vision impairments), or if the user chooses not to display images, or if the browser is unable to display the image because it is invalid or an unsupported type. In these cases, the browser may replace the image with the text defined in this element's alt attribute.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="src">
        <TITRE>image URL</TITRE>
        <DOCUMENTATION>
          Image URL.
          On browsers supporting srcset, src is ignored if this one is provided.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="alt">
        <TITRE>alt</TITRE>
        <DOCUMENTATION>
          This attribute defines the alternative text describing the image. Users will see this displayed if the image URL is wrong, the image is not in one of the supported formats, or until the image is downloaded.
          
          Usage note: Omitting this attribute indicates that the image is a key part of the content, but no textual equivalent is available. Setting this attribute to the empty string indicates that this image is not a key part of the content; non-visual browsers may omit it from rendering.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="width">
        <TITRE>width</TITRE>
        <DOCUMENTATION>
          The width of the image in pixels or percent.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="height">
        <TITRE>height</TITRE>
        <DOCUMENTATION>
          The height of the image in pixels or percent.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="TeXwidth">
        <TITRE>TeX width (mm/%)</TITRE>
        <DOCUMENTATION>
          Allows you to set the width of the image, in mm or %, as it will be rendered into the LaTeX document used to print the problem.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="TeXheight">
        <TITRE>TeX height (mm)</TITRE>
        <DOCUMENTATION>
          Allows you to set the height of the image, in mm, as it will be rendered into the LaTeX document used to print the problem.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="align">
        <TITRE>align</TITRE>
        <DOCUMENTATION>
          This attribute is deprecated since HTML 4.01 and obsolete since HTML5. Use the vertical-align CSS property instead.
          
          Specifies the alignment of the image relative to the enclosing text paragraph:
          - bottom: The image will be aligned so that its bottom will be at the baseline of the surrounding text.
          - middle: The image will be aligned so that its center-line will be at the baseline of the surrounding text.
          - top: The image will be aligned so that its top will be at the baseline of the surrounding text.
          - left: The image will be placed so that it is at the left of the surrounding text. The surrounding text will fill in the region to the right of the image.
          - right: The image will be placed so that it is at the right of the surrounding text. The surrounding text will fill in the region to the left of the image.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="TeXwrap">
        <TITRE>TeX wrap</TITRE>
        <DOCUMENTATION>
          Allows you to select how the LaTeX document will attempt to wrap text around a horizontally aligned image.
          parbox: \newline and \parbox will be used to place the image. This method ensures that text will not be wrapped on top of the image, however very little text will appear next to the image itself.
          parpic: The picins package \parpic command will be used to place the image. This will wrap the remainder of the paragraph containing the picture around the image.
          If, however, there is insufficient text to fill the space to the left or right of the image, the next paragraph may be wrapped on top of the image. In addition, \parpic does not always honor the end of the page, causing the image to extend below the page footer.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="encrypturl">
        <TITRE>encrypt url</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="figure">
      <TITRE>Figure</TITRE>
      <DOCUMENTATION>
        The HTML <figure> Element represents self-contained content, frequently with a caption (<figcaption>), and is typically referenced as a single unit. While it is related to the main flow, its position is independent of the main flow. Usually this is an image, an illustration, a diagram, a code snippet, or a schema that is referenced in the main text, but that can be moved to another page or to an appendix without affecting the main flow.
        
        Usage note: A caption can be associated with the <figure> element by inserting a <figcaption> inside it (as the first or the last child).
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="figcaption">
      <TITRE>Figure Caption</TITRE>
      <DOCUMENTATION>
        The HTML <figcaption> Element represents a caption or a legend associated with a figure or an illustration described by the rest of the data of the <figure> element which is its immediate ancestor which means <figcaption> can be the first or last element inside a <figure> block. Also, the HTML Figcaption Element is optional; if not provided, then the parent figure element will have no caption.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="object">
      <TITRE>Object</TITRE>
      <DOCUMENTATION>
        The HTML <object> Element (or HTML Embedded Object Element) represents an external resource, which can be treated as an image, a nested browsing context, or a resource to be handled by a plugin.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="classid">
        <TITRE>classid</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="codebase">
        <TITRE>codebase</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="data">
        <TITRE>data</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="type">
        <TITRE>type</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="codetype">
        <TITRE>codetype</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="archive">
        <TITRE>archive</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="standby">
        <TITRE>standby</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="width">
        <TITRE>width</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="height">
        <TITRE>height</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="usemap">
        <TITRE>usemap</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="param">
      <TITRE>Parameter</TITRE>
      <DOCUMENTATION>
        param is used to supply a named property value
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
        <DOCUMENTATION>
          Name of the parameter.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="value">
        <TITRE>value</TITRE>
        <DOCUMENTATION>
          Specifies the value of the parameter.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="valuetype">
        <TITRE>valuetype</TITRE>
        <DOCUMENTATION>
          Obsolete in HTML5.
          
          Specifies the type of the value attribute.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="type">
        <TITRE>type</TITRE>
        <DOCUMENTATION>
          Obsolete in HTML5.
          
          Only used if the valuetype is set to "ref". Specifies the MIME type of values found at the URI specified by value.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="embed">
      <TITRE>Embedded</TITRE>
      <DOCUMENTATION>
        The HTML <embed> Element represents an integration point for an external application or interactive content (in other words, a plug-in).
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="src">
        <TITRE>src</TITRE>
        <DOCUMENTATION>
          The URL of the resource being embedded.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="type">
        <TITRE>type</TITRE>
        <DOCUMENTATION>
          The MIME type to use to select the plug-in to instantiate.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="width">
        <TITRE>width</TITRE>
        <DOCUMENTATION>
          The displayed width of the resource, in CSS pixels.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="height">
        <TITRE>height</TITRE>
        <DOCUMENTATION>
          The displayed height of the resource, in CSS pixels.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="applet">
      <TITRE>Java Applet</TITRE>
      <DOCUMENTATION>
        This feature is obsolete. Although it may still work in some browsers, its use is discouraged since it could be removed at any time. Try to avoid using it.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="codebase">
        <TITRE>codebase</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="archive">
        <TITRE>archive</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="code">
        <TITRE>code</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="object">
        <TITRE>object</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="alt">
        <TITRE>alt</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="width">
        <TITRE>width</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="height">
        <TITRE>height</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="hspace">
        <TITRE>hspace</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="vspace">
        <TITRE>vspace</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="video">
      <TITRE>Video</TITRE>
      <DOCUMENTATION>
        The HTML <video> element is used to embed video content. It may contain several video sources, represented using the src attribute or the <source> element; the browser will choose the most suitable one.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="src">
        <TITRE>src</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="width">
        <TITRE>width</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="height">
        <TITRE>height</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="autoplay">
        <TITRE>autoplay</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="controls">
        <TITRE>controls</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="loop">
        <TITRE>loop</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="source">
      <TITRE>Source</TITRE>
      <DOCUMENTATION>
        The HTML <source> element is used to specify multiple media resources for <picture>, <audio> and <video> elements. It is an empty element. It is commonly used to serve the same media in multiple formats supported by different browsers.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="src">
        <TITRE>src</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="type">
        <TITRE>type</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="audio">
      <TITRE>Audio</TITRE>
      <DOCUMENTATION>
        The HTML <audio> element is used to embed sound content in documents. It may contain several audio sources, represented using the src attribute or the <source> element; the browser will choose the most suitable one.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="src">
        <TITRE>src</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="autoplay">
        <TITRE>autoplay</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="controls">
        <TITRE>controls</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="loop">
        <TITRE>loop</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="map">
      <TITRE>Map</TITRE>
      <DOCUMENTATION>
        The HTML <map> element is used with <area> elements to define an image map (a clickable link area).
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="class">
        <TITRE>class</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="style">
        <TITRE>style</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="title">
        <TITRE>title</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="area">
      <TITRE>Area</TITRE>
      <DOCUMENTATION>
        The HTML <area> element defines a hot-spot region on an image, and optionally associates it with a hypertext link. This element is used only within a <map> element.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="shape">
        <TITRE>shape</TITRE>
        <DOCUMENTATION>
          The shape of the associated hot spot. The specifications for HTML 5 and HTML 4 define the values rect, which defines a rectangular region; circle, which defines a circular region; poly, which defines a polygon; and default, which indicates the entire region beyond any defined shapes.
          Many browsers, notably Internet Explorer 4 and higher, support circ, polygon, and rectangle as valid values for shape; these values are *not standard*.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="coords">
        <TITRE>coords</TITRE>
        <DOCUMENTATION>
          A set of values specifying the coordinates of the hot-spot region. The number and meaning of the values depend upon the value specified for the shape attribute. For a rect or rectangle shape, the coords value is two x,y pairs: left, top, right, and bottom. For a circle shape, the value is x,y,r where x,y is a pair specifying the center of the circle and r is a value for the radius. For a poly or polygon< shape, the value is a set of x,y pairs for each point in the polygon: x1,y1,x2,y2,x3,y3, and so on. In HTML4, the values are numbers of pixels or percentages, if a percent sign (%) is appended; in HTML5, the values are numbers of CSS pixels.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="href">
        <TITRE>href</TITRE>
        <DOCUMENTATION>
          The hyperlink target for the area. Its value is a valid URL. In HTML4, either this attribute or the nohref attribute must be present in the element. In HTML5, this attribute may be omitted; if so, the area element does not represent a hyperlink.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="nohref">
        <TITRE>nohref</TITRE>
        <DOCUMENTATION>
          Indicates that no hyperlink exists for the associated area. Either this attribute or the href attribute must be present in the element.
          
          Usage note: This attribute is obsolete in HTML5, instead omitting the href attribute is sufficient.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="alt">
        <TITRE>alt</TITRE>
        <DOCUMENTATION>
          A text string alternative to display on browsers that do not display images. The text should be phrased so that it presents the user with the same kind of choice as the image would offer when displayed without the alternative text. In HTML4, this attribute is required, but may be the empty string (""). In HTML5, this attribute is required only if the href attribute is used.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="canvas">
      <TITRE>Canvas</TITRE>
      <DOCUMENTATION>
        The HTML <canvas> Element can be used to draw graphics via scripting (usually JavaScript). For example, it can be used to draw graphs, make photo compositions or even perform animations. You may (and should) provide alternate content inside the <canvas> block. That content will be rendered both on older browsers that don't support canvas and in browsers with JavaScript disabled.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="id">
        <TITRE>id</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="class">
        <TITRE>class</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="style">
        <TITRE>style</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="width">
        <TITRE>width</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="height">
        <TITRE>height</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="form">
      <TITRE>Form</TITRE>
      <DOCUMENTATION>
        The HTML <form> element represents a document section that contains interactive controls to submit information to a web server.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="action">
        <TITRE>action</TITRE>
        <DOCUMENTATION>
          The URI of a program that processes the form information.
          
          In HTML5, the action attribute is no longer required.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="method">
        <TITRE>method</TITRE>
        <DOCUMENTATION>
          The HTTP method that the browser uses to submit the form. Possible values are:
          
          - post: Corresponds to the HTTP POST method ; form data are included in the body of the form and sent to the server.
          
          - get: Corresponds to the HTTP GET method; form data are appended to the action attribute URI with a '?' as separator, and the resulting URI is sent to the server. Use this method when the form has no side-effects and contains only ASCII characters.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="enctype">
        <TITRE>enctype</TITRE>
        <DOCUMENTATION>
          When the value of the method attribute is post, enctype is the MIME type of content that is used to submit the form to the server. Possible values are:
          
          - application/x-www-form-urlencoded: The default value if the attribute is not specified.
          - multipart/form-data: The value used for an <input> element with the type attribute set to "file".
          - text/plain (HTML5)
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="accept-charset">
        <TITRE>accept-charset</TITRE>
        <DOCUMENTATION>
          A space- or comma-delimited list of character encodings that the server accepts. The browser uses them in the order in which they are listed. The default value, the reserved string "UNKNOWN", indicates the same encoding as that of the document containing the form element.
          
          In previous versions of HTML, the different character encodings could be delimited by spaces or commas. In HTML5, only spaces are allowed as delimiters.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>Name</TITRE>
        <DOCUMENTATION>
          The name of the form. In HTML 4, its use is deprecated (id should be used instead). It must be unique among the forms in a document and not just an empty string in HTML 5.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="accept">
        <TITRE>accept</TITRE>
        <DOCUMENTATION>
          A comma-separated list of content types that the server accepts.
          
          Usage note: This attribute has been removed in HTML5 and should no longer be used. Instead, use the accept attribute of the specific <input> element.
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="onsubmit">
        <TITRE>onsubmit</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="onreset">
        <TITRE>onreset</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="input">
      <TITRE>Form Control</TITRE>
      <DOCUMENTATION>
        The HTML <input> element is used to create interactive controls for web-based forms.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="type">
        <TITRE>type</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
        <DOCUMENTATION>
          the name attribute is required for all but submit & reset
        </DOCUMENTATION>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="value">
        <TITRE>value</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="checked">
        <TITRE>checked</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="disabled">
        <TITRE>disabled</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="readonly">
        <TITRE>readonly</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="size">
        <TITRE>size</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="maxlength">
        <TITRE>maxlength</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="src">
        <TITRE>src</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="alt">
        <TITRE>alt</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="usemap">
        <TITRE>usemap</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="onselect">
        <TITRE>onselect</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="onchange">
        <TITRE>onchange</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="accept">
        <TITRE>accept</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="select">
      <TITRE>Option Selector</TITRE>
      <DOCUMENTATION>
        The HTML select (<select>) element represents a control that presents a menu of options. The options within the menu are represented by <option> elements, which can be grouped by <optgroup> elements. Options can be pre-selected for the user.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="size">
        <TITRE>size</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="multiple">
        <TITRE>multiple</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="disabled">
        <TITRE>disabled</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="tabindex">
        <TITRE>tabindex</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="onfocus">
        <TITRE>onfocus</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="onblur">
        <TITRE>onblur</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="onchange">
        <TITRE>onchange</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="optgroup">
      <TITRE>Option Group</TITRE>
      <DOCUMENTATION>
        In a Web form, the HTML <optgroup> element creates a grouping of options within a <select> element.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="disabled">
        <TITRE>disabled</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="label">
        <TITRE>label</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="option">
      <TITRE>Option</TITRE>
      <DOCUMENTATION>
        In a Web form, the HTML <option> element is used to create a control representing an item within a <select>, an <optgroup> or a <datalist> HTML5 element.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="selected">
        <TITRE>selected</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="disabled">
        <TITRE>disabled</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="label">
        <TITRE>label</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="value">
        <TITRE>value</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="textarea">
      <TITRE>Multi-line Text Field</TITRE>
      <DOCUMENTATION>
        The HTML <textarea> element represents a multi-line plain-text editing control.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="rows">
        <TITRE>rows</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="cols">
        <TITRE>cols</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="disabled">
        <TITRE>disabled</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="readonly">
        <TITRE>readonly</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="onselect">
        <TITRE>onselect</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="onchange">
        <TITRE>onchange</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="fieldset">
      <TITRE>Control Group</TITRE>
      <DOCUMENTATION>
        The HTML <fieldset> element is used to group several controls as well as labels (<label>) within a web form.
        
        Only one legend element should occur in the content, and if present should only be preceded by whitespace.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="legend">
      <TITRE>Legend</TITRE>
      <DOCUMENTATION>
        The HTML <legend> Element (or HTML Legend Field Element) represents a caption for the content of its parent <fieldset>.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="button">
      <TITRE>Button</TITRE>
      <DOCUMENTATION>
        The HTML <button> Element represents a clickable button.
      </DOCUMENTATION>
      <STRINGS_ATTRIBUT attribut="name">
        <TITRE>name</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="value">
        <TITRE>value</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="type">
        <TITRE>type</TITRE>
      </STRINGS_ATTRIBUT>
      <STRINGS_ATTRIBUT attribut="disabled">
        <TITRE>disabled</TITRE>
      </STRINGS_ATTRIBUT>
    </STRINGS_ELEMENT>
    <STRINGS_ELEMENT element="iframe">
      <TITRE>Inline Frame</TITRE>
      <DOCUMENTATION>
        The HTML <iframe> Element (or HTML inline frame element) represents a nested browsing context, effectively embedding another HTML page into the current page.
      </DOCUMENTATION>
    </STRINGS_ELEMENT>
  </STRINGS>
</CONFIG_JAXE>
Index: modules/damieng/graphical_editor/loncapa_daxe/web/config/xhtml1-strict.xsd
+++ modules/damieng/graphical_editor/loncapa_daxe/web/config/xhtml1-strict.xsd
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema version="1.0" xml:lang="en"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    targetNamespace="http://www.w3.org/1999/xhtml"
    xmlns="http://www.w3.org/1999/xhtml"
    xmlns:xml="http://www.w3.org/XML/1998/namespace"
    elementFormDefault="qualified">

  <xs:annotation>
    <xs:documentation>
    XHTML 1.0 (Second Edition) Strict in XML Schema

    This is the same as HTML 4 Strict except for
    changes due to the differences between XML and SGML.

    Namespace = http://www.w3.org/1999/xhtml

    For further information, see: http://www.w3.org/TR/xhtml1

    Copyright (c) 1998-2002 W3C (MIT, INRIA, Keio),
    All Rights Reserved. 

    The DTD version is identified by the PUBLIC and SYSTEM identifiers:

    PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"

    $Id: xhtml1-strict.xsd,v 1.1 2015/04/17 15:35:06 damieng Exp $
    </xs:documentation>
  </xs:annotation>

<!--
  <xs:import namespace="http://www.w3.org/XML/1998/namespace"
      schemaLocation="http://www.w3.org/2001/xml.xsd"/>
-->
  <xs:import namespace="http://www.w3.org/XML/1998/namespace"
      schemaLocation="xml.xsd"/>

  <xs:annotation>
    <xs:documentation>
    ================ Character mnemonic entities =========================

    XHTML entity sets are identified by the PUBLIC and SYSTEM identifiers:
  
    PUBLIC "-//W3C//ENTITIES Latin 1 for XHTML//EN"
    SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent"

    PUBLIC "-//W3C//ENTITIES Special for XHTML//EN"
    SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml-special.ent"

    PUBLIC "-//W3C//ENTITIES Symbols for XHTML//EN"
    SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent"
    </xs:documentation>
  </xs:annotation>

  <xs:annotation>
    <xs:documentation>
    ================== Imported Names ====================================
    </xs:documentation>
  </xs:annotation>

  <xs:simpleType name="ContentType">
    <xs:annotation>
      <xs:documentation>
      media type, as per [RFC2045]
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string"/>
  </xs:simpleType>

  <xs:simpleType name="ContentTypes">
    <xs:annotation>
      <xs:documentation>
      comma-separated list of media types, as per [RFC2045]
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string"/>
  </xs:simpleType>

  <xs:simpleType name="Charset">
    <xs:annotation>
      <xs:documentation>
      a character encoding, as per [RFC2045]
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string"/>
  </xs:simpleType>

  <xs:simpleType name="Charsets">
    <xs:annotation>
      <xs:documentation>
      a space separated list of character encodings, as per [RFC2045]
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string"/>
  </xs:simpleType>

  <xs:simpleType name="LanguageCode">
    <xs:annotation>
      <xs:documentation>
      a language code, as per [RFC3066]
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:language"/>
  </xs:simpleType>

  <xs:simpleType name="Character">
    <xs:annotation>
      <xs:documentation>
      a single character, as per section 2.2 of [XML]
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string">
      <xs:length value="1" fixed="true"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="Number">
    <xs:annotation>
      <xs:documentation>
      one or more digits
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:nonNegativeInteger">
      <xs:pattern value="[0-9]+"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="tabindexNumber">
    <xs:annotation>
      <xs:documentation>
      tabindex attribute specifies the position of the current element
      in the tabbing order for the current document. This value must be
      a number between 0 and 32767. User agents should ignore leading zeros. 
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="Number">
      <xs:minInclusive value="0"/>
      <xs:maxInclusive value="32767"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="LinkTypes">
    <xs:annotation>
      <xs:documentation>
      space-separated list of link types
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:NMTOKENS"/>
  </xs:simpleType>

  <xs:simpleType name="MediaDesc">
    <xs:annotation>
      <xs:documentation>
      single or comma-separated list of media descriptors
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string">
      <xs:pattern value="[^,]+(,\s*[^,]+)*"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="URI">
    <xs:annotation>
      <xs:documentation>
      a Uniform Resource Identifier, see [RFC2396]
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:anyURI"/>
  </xs:simpleType>

  <xs:simpleType name="UriList">
    <xs:annotation>
      <xs:documentation>
      a space separated list of Uniform Resource Identifiers
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string"/>
  </xs:simpleType>

  <xs:simpleType name="Datetime">
    <xs:annotation>
      <xs:documentation>
      date and time information. ISO date format
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:dateTime"/>
  </xs:simpleType>

  <xs:simpleType name="Script">
    <xs:annotation>
      <xs:documentation>
      script expression
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string"/>
  </xs:simpleType>

  <xs:simpleType name="StyleSheet">
    <xs:annotation>
      <xs:documentation>
      style sheet data
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string"/>
  </xs:simpleType>

  <xs:simpleType name="Text">
    <xs:annotation>
      <xs:documentation>
      used for titles etc.
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string"/>
  </xs:simpleType>

  <xs:simpleType name="Length">
    <xs:annotation>
      <xs:documentation>
      nn for pixels or nn% for percentage length
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string">
      <xs:pattern value="[\-+]?(\d+|\d+(\.\d+)?%)"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="MultiLength">
    <xs:annotation>
      <xs:documentation>
      pixel, percentage, or relative
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string">
      <xs:pattern value="[\-+]?(\d+|\d+(\.\d+)?%)|[1-9]?(\d+)?\*"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="Pixels">
    <xs:annotation>
      <xs:documentation>
      integer representing length in pixels
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:nonNegativeInteger"/>
  </xs:simpleType>

  <xs:annotation>
    <xs:documentation>
    these are used for image maps
    </xs:documentation>
  </xs:annotation>

  <xs:simpleType name="Shape">
    <xs:restriction base="xs:token">
      <xs:enumeration value="rect"/>
      <xs:enumeration value="circle"/>
      <xs:enumeration value="poly"/>
      <xs:enumeration value="default"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="Coords">
    <xs:annotation>
      <xs:documentation>
      comma separated list of lengths
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string">
      <xs:pattern
          value="[\-+]?(\d+|\d+(\.\d+)?%)(,\s*[\-+]?(\d+|\d+(\.\d+)?%))*"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:annotation>
    <xs:documentation>
    =================== Generic Attributes ===============================
    </xs:documentation>
  </xs:annotation>

  <xs:attributeGroup name="coreattrs">
    <xs:annotation>
      <xs:documentation>
      core attributes common to most elements
      id       document-wide unique id
      class    space separated list of classes
      style    associated style info
      title    advisory title/amplification
      </xs:documentation>
    </xs:annotation>
    <xs:attribute name="id" type="xs:ID"/>
    <xs:attribute name="class" type="xs:NMTOKENS"/>
    <xs:attribute name="style" type="StyleSheet"/>
    <xs:attribute name="title" type="Text"/>
  </xs:attributeGroup>

  <xs:attributeGroup name="i18n">
    <xs:annotation>
      <xs:documentation>
      internationalization attributes
      lang        language code (backwards compatible)
      xml:lang    language code (as per XML 1.0 spec)
      dir         direction for weak/neutral text
      </xs:documentation>
    </xs:annotation>
    <xs:attribute name="lang" type="LanguageCode"/>
    <xs:attribute ref="xml:lang"/>
    <xs:attribute name="dir">
      <xs:simpleType>
        <xs:restriction base="xs:token">
          <xs:enumeration value="ltr"/>
          <xs:enumeration value="rtl"/>
        </xs:restriction>
      </xs:simpleType>
    </xs:attribute>
  </xs:attributeGroup>

  <xs:attributeGroup name="events">
    <xs:annotation>
      <xs:documentation>
      attributes for common UI events
      onclick     a pointer button was clicked
      ondblclick  a pointer button was double clicked
      onmousedown a pointer button was pressed down
      onmouseup   a pointer button was released
      onmousemove a pointer was moved onto the element
      onmouseout  a pointer was moved away from the element
      onkeypress  a key was pressed and released
      onkeydown   a key was pressed down
      onkeyup     a key was released
      </xs:documentation>
    </xs:annotation>
    <xs:attribute name="onclick" type="Script"/>
    <xs:attribute name="ondblclick" type="Script"/>
    <xs:attribute name="onmousedown" type="Script"/>
    <xs:attribute name="onmouseup" type="Script"/>
    <xs:attribute name="onmouseover" type="Script"/>
    <xs:attribute name="onmousemove" type="Script"/>
    <xs:attribute name="onmouseout" type="Script"/>
    <xs:attribute name="onkeypress" type="Script"/>
    <xs:attribute name="onkeydown" type="Script"/>
    <xs:attribute name="onkeyup" type="Script"/>
  </xs:attributeGroup>

  <xs:attributeGroup name="focus">
    <xs:annotation>
      <xs:documentation>
      attributes for elements that can get the focus
      accesskey   accessibility key character
      tabindex    position in tabbing order
      onfocus     the element got the focus
      onblur      the element lost the focus
      </xs:documentation>
    </xs:annotation>
    <xs:attribute name="accesskey" type="Character"/>
    <xs:attribute name="tabindex" type="tabindexNumber"/>
    <xs:attribute name="onfocus" type="Script"/>
    <xs:attribute name="onblur" type="Script"/>
  </xs:attributeGroup>

  <xs:attributeGroup name="attrs">
    <xs:attributeGroup ref="coreattrs"/>
    <xs:attributeGroup ref="i18n"/>
    <xs:attributeGroup ref="events"/>
  </xs:attributeGroup>

  <xs:annotation>
    <xs:documentation>
    =================== Text Elements ====================================
    </xs:documentation>
  </xs:annotation>

  <xs:group name="special.pre">
    <xs:choice>
      <xs:element ref="br"/>
      <xs:element ref="span"/>
      <xs:element ref="bdo"/>
      <xs:element ref="map"/>
    </xs:choice>
  </xs:group>

  <xs:group name="special">
    <xs:choice>
      <xs:group ref="special.pre"/>
      <xs:element ref="object"/>
      <xs:element ref="img"/>
    </xs:choice>
  </xs:group>

  <xs:group name="fontstyle">
    <xs:choice>
      <xs:element ref="tt"/>
      <xs:element ref="i"/>
      <xs:element ref="b"/>
      <xs:element ref="big"/>
      <xs:element ref="small"/>
    </xs:choice>
  </xs:group>

  <xs:group name="phrase">
    <xs:choice>
      <xs:element ref="em"/>
      <xs:element ref="strong"/>
      <xs:element ref="dfn"/>
      <xs:element ref="code"/>
      <xs:element ref="q"/>
      <xs:element ref="samp"/>
      <xs:element ref="kbd"/>
      <xs:element ref="var"/>
      <xs:element ref="cite"/>
      <xs:element ref="abbr"/>
      <xs:element ref="acronym"/>
      <xs:element ref="sub"/>
      <xs:element ref="sup"/>
    </xs:choice>
  </xs:group>

  <xs:group name="inline.forms">
    <xs:choice>
      <xs:element ref="input"/>
      <xs:element ref="select"/>
      <xs:element ref="textarea"/>
      <xs:element ref="label"/>
      <xs:element ref="button"/>
    </xs:choice>
  </xs:group>

  <xs:group name="misc.inline">
    <xs:choice>
      <xs:element ref="ins"/>
      <xs:element ref="del"/>
      <xs:element ref="script"/>
    </xs:choice>
  </xs:group>

  <xs:group name="misc">
    <xs:annotation>
      <xs:documentation>
      these can only occur at block level
      </xs:documentation>
    </xs:annotation>
    <xs:choice>
      <xs:element ref="noscript"/>
      <xs:group ref="misc.inline"/>
    </xs:choice>
  </xs:group>

  <xs:group name="inline">
    <xs:choice>
      <xs:element ref="a"/>
      <xs:group ref="special"/>
      <xs:group ref="fontstyle"/>
      <xs:group ref="phrase"/>
      <xs:group ref="inline.forms"/>
    </xs:choice>
  </xs:group>

  <xs:complexType name="Inline" mixed="true">
    <xs:annotation>
      <xs:documentation>
      "Inline" covers inline or "text-level" elements
      </xs:documentation>
    </xs:annotation>
    <xs:choice minOccurs="0" maxOccurs="unbounded">
      <xs:group ref="inline"/>
      <xs:group ref="misc.inline"/>
    </xs:choice>
  </xs:complexType>

  <xs:annotation>
    <xs:documentation>
    ================== Block level elements ==============================
    </xs:documentation>
  </xs:annotation>

  <xs:group name="heading">
    <xs:choice>
      <xs:element ref="h1"/>
      <xs:element ref="h2"/>
      <xs:element ref="h3"/>
      <xs:element ref="h4"/>
      <xs:element ref="h5"/>
      <xs:element ref="h6"/>
    </xs:choice>
  </xs:group>

  <xs:group name="lists">
    <xs:choice>
      <xs:element ref="ul"/>
      <xs:element ref="ol"/>
      <xs:element ref="dl"/>
    </xs:choice>
  </xs:group>

  <xs:group name="blocktext">
    <xs:choice>
      <xs:element ref="pre"/>
      <xs:element ref="hr"/>
      <xs:element ref="blockquote"/>
      <xs:element ref="address"/>
    </xs:choice>
  </xs:group>

  <xs:group name="block">
    <xs:choice>
      <xs:element ref="p"/>
      <xs:group ref="heading"/>
      <xs:element ref="div"/>
      <xs:group ref="lists"/>
      <xs:group ref="blocktext"/>
      <xs:element ref="fieldset"/>
      <xs:element ref="table"/>
    </xs:choice>
  </xs:group>

  <xs:complexType name="Block">
    <xs:choice minOccurs="0" maxOccurs="unbounded">
      <xs:group ref="block"/>
      <xs:element ref="form"/>
      <xs:group ref="misc"/>
    </xs:choice>
  </xs:complexType>

  <xs:complexType name="Flow" mixed="true">
    <xs:annotation>
      <xs:documentation>
      "Flow" mixes block and inline and is used for list items etc.
      </xs:documentation>
    </xs:annotation>
    <xs:choice minOccurs="0" maxOccurs="unbounded">
      <xs:group ref="block"/>
      <xs:element ref="form"/>
      <xs:group ref="inline"/>
      <xs:group ref="misc"/>
    </xs:choice>
  </xs:complexType>

  <xs:annotation>
    <xs:documentation>
    ================== Content models for exclusions =====================
    </xs:documentation>
  </xs:annotation>

  <xs:complexType name="a.content" mixed="true">
    <xs:annotation>
      <xs:documentation>
      a elements use "Inline" excluding a
      </xs:documentation>
    </xs:annotation>
    <xs:choice minOccurs="0" maxOccurs="unbounded">
      <xs:group ref="special"/>
      <xs:group ref="fontstyle"/>
      <xs:group ref="phrase"/>
      <xs:group ref="inline.forms"/>
      <xs:group ref="misc.inline"/>
    </xs:choice>
  </xs:complexType>

  <xs:complexType name="pre.content" mixed="true">
    <xs:annotation>
      <xs:documentation>
      pre uses "Inline" excluding big, small, sup or sup
      </xs:documentation>
    </xs:annotation>
    <xs:choice minOccurs="0" maxOccurs="unbounded">
      <xs:element ref="a"/>
      <xs:group ref="fontstyle"/>
      <xs:group ref="phrase"/>
      <xs:group ref="special.pre"/>
      <xs:group ref="misc.inline"/>
      <xs:group ref="inline.forms"/>
    </xs:choice>
  </xs:complexType>

  <xs:complexType name="form.content">
    <xs:annotation>
      <xs:documentation>
      form uses "Block" excluding form
      </xs:documentation>
    </xs:annotation>
    <xs:choice minOccurs="0" maxOccurs="unbounded">
      <xs:group ref="block"/>
      <xs:group ref="misc"/>
    </xs:choice>
  </xs:complexType>

  <xs:complexType name="button.content" mixed="true">
    <xs:annotation>
      <xs:documentation>
      button uses "Flow" but excludes a, form and form controls
      </xs:documentation>
    </xs:annotation>
    <xs:choice minOccurs="0" maxOccurs="unbounded">
      <xs:element ref="p"/>
      <xs:group ref="heading"/>
      <xs:element ref="div"/>
      <xs:group ref="lists"/>
      <xs:group ref="blocktext"/>
      <xs:element ref="table"/>
      <xs:group ref="special"/>
      <xs:group ref="fontstyle"/>
      <xs:group ref="phrase"/>
      <xs:group ref="misc"/>
    </xs:choice>
  </xs:complexType>

  <xs:annotation>
    <xs:documentation>
    ================ Document Structure ==================================
    </xs:documentation>
  </xs:annotation>

  <xs:element name="html">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="head"/>
        <xs:element ref="body"/>
      </xs:sequence>
      <xs:attributeGroup ref="i18n"/>
      <xs:attribute name="id" type="xs:ID"/>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    ================ Document Head =======================================
    </xs:documentation>
  </xs:annotation>

  <xs:group name="head.misc">
    <xs:sequence>
      <xs:choice minOccurs="0" maxOccurs="unbounded">
        <xs:element ref="script"/>
        <xs:element ref="style"/>
        <xs:element ref="meta"/>
        <xs:element ref="link"/>
        <xs:element ref="object"/>
      </xs:choice>
    </xs:sequence>
  </xs:group>

  <xs:element name="head">
    <xs:annotation>
      <xs:documentation>
      content model is "head.misc" combined with a single
      title and an optional base element in any order
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:sequence>
        <xs:group ref="head.misc"/>
        <xs:choice>
          <xs:sequence>
            <xs:element ref="title"/>
            <xs:group ref="head.misc"/>
            <xs:sequence minOccurs="0">
              <xs:element ref="base"/>
              <xs:group ref="head.misc"/>
            </xs:sequence>
          </xs:sequence>
          <xs:sequence>
            <xs:element ref="base"/>
            <xs:group ref="head.misc"/>
            <xs:element ref="title"/>
            <xs:group ref="head.misc"/>
          </xs:sequence>
        </xs:choice>
      </xs:sequence>
      <xs:attributeGroup ref="i18n"/>
      <xs:attribute name="id" type="xs:ID"/>
      <xs:attribute name="profile" type="URI"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="title">
    <xs:annotation>
      <xs:documentation>
      The title element is not considered part of the flow of text.
      It should be displayed, for example as the page header or
      window title. Exactly one title is required per document.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:attributeGroup ref="i18n"/>
      <xs:attribute name="id" type="xs:ID"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="base">
    <xs:annotation>
      <xs:documentation>
      document base URI
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="href" use="required" type="URI"/>
      <xs:attribute name="id" type="xs:ID"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="meta">
    <xs:annotation>
      <xs:documentation>
      generic metainformation
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attributeGroup ref="i18n"/>
      <xs:attribute name="id" type="xs:ID"/>
      <xs:attribute name="http-equiv"/>
      <xs:attribute name="name"/>
      <xs:attribute name="content" use="required"/>
      <xs:attribute name="scheme"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="link">
    <xs:annotation>
      <xs:documentation>
      Relationship values can be used in principle:

      a) for document specific toolbars/menus when used
         with the link element in document head e.g.
           start, contents, previous, next, index, end, help
      b) to link to a separate style sheet (rel="stylesheet")
      c) to make a link to a script (rel="script")
      d) by stylesheets to control how collections of
         html nodes are rendered into printed documents
      e) to make a link to a printable version of this document
         e.g. a PostScript or PDF version (rel="alternate" media="print")
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attributeGroup ref="attrs"/>
      <xs:attribute name="charset" type="Charset"/>
      <xs:attribute name="href" type="URI"/>
      <xs:attribute name="hreflang" type="LanguageCode"/>
      <xs:attribute name="type" type="ContentType"/>
      <xs:attribute name="rel" type="LinkTypes"/>
      <xs:attribute name="rev" type="LinkTypes"/>
      <xs:attribute name="media" type="MediaDesc"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="style">
    <xs:annotation>
      <xs:documentation>
      style info, which may include CDATA sections
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:attributeGroup ref="i18n"/>
      <xs:attribute name="id" type="xs:ID"/>
      <xs:attribute name="type" use="required" type="ContentType"/>
      <xs:attribute name="media" type="MediaDesc"/>
      <xs:attribute name="title" type="Text"/>
      <xs:attribute ref="xml:space" fixed="preserve"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="script">
    <xs:annotation>
      <xs:documentation>
      script statements, which may include CDATA sections
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:attribute name="id" type="xs:ID"/>
      <xs:attribute name="charset" type="Charset"/>
      <xs:attribute name="type" use="required" type="ContentType"/>
      <xs:attribute name="src" type="URI"/>
      <xs:attribute name="defer">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="defer"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute ref="xml:space" fixed="preserve"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="noscript">
    <xs:annotation>
      <xs:documentation>
      alternate content container for non script-based rendering
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:complexContent>
        <xs:extension base="Block">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    =================== Document Body ====================================
    </xs:documentation>
  </xs:annotation>

  <xs:element name="body">
    <xs:complexType>
      <xs:complexContent>
        <xs:extension base="Block">
          <xs:attributeGroup ref="attrs"/>
          <xs:attribute name="onload" type="Script"/>
          <xs:attribute name="onunload" type="Script"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="div">
    <xs:annotation>
      <xs:documentation>
      generic language/style container      
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Flow">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    =================== Paragraphs =======================================
    </xs:documentation>
  </xs:annotation>

  <xs:element name="p">
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    =================== Headings =========================================

    There are six levels of headings from h1 (the most important)
    to h6 (the least important).
    </xs:documentation>
  </xs:annotation>

  <xs:element name="h1">
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="h2">
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="h3">
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="h4">
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="h5">
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="h6">
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    =================== Lists ============================================
    </xs:documentation>
  </xs:annotation>

  <xs:element name="ul">
    <xs:annotation>
      <xs:documentation>
      Unordered list
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" ref="li"/>
      </xs:sequence>
      <xs:attributeGroup ref="attrs"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="ol">
    <xs:annotation>
      <xs:documentation>
      Ordered (numbered) list
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" ref="li"/>
      </xs:sequence>
      <xs:attributeGroup ref="attrs"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="li">
    <xs:annotation>
      <xs:documentation>
      list item
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Flow">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    definition lists - dt for term, dd for its definition
    </xs:documentation>
  </xs:annotation>

  <xs:element name="dl">
    <xs:complexType>
      <xs:choice maxOccurs="unbounded">
        <xs:element ref="dt"/>
        <xs:element ref="dd"/>
      </xs:choice>
      <xs:attributeGroup ref="attrs"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="dt">
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="dd">
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Flow">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    =================== Address ==========================================
    </xs:documentation>
  </xs:annotation>

  <xs:element name="address">
    <xs:annotation>
      <xs:documentation>
      information on author
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    =================== Horizontal Rule ==================================
    </xs:documentation>
  </xs:annotation>

  <xs:element name="hr">
    <xs:complexType>
      <xs:attributeGroup ref="attrs"/>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    =================== Preformatted Text ================================
    </xs:documentation>
  </xs:annotation>

  <xs:element name="pre">
    <xs:annotation>
      <xs:documentation>
      content is "Inline" excluding "img|object|big|small|sub|sup"
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="pre.content">
           <xs:attributeGroup ref="attrs"/>
           <xs:attribute ref="xml:space" fixed="preserve"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    =================== Block-like Quotes ================================
    </xs:documentation>
  </xs:annotation>

  <xs:element name="blockquote">
    <xs:complexType>
      <xs:complexContent>
        <xs:extension base="Block">
          <xs:attributeGroup ref="attrs"/>
          <xs:attribute name="cite" type="URI"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    =================== Inserted/Deleted Text ============================

    ins/del are allowed in block and inline content, but its
    inappropriate to include block content within an ins element
    occurring in inline content.
    </xs:documentation>
  </xs:annotation>

  <xs:element name="ins">
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Flow">
          <xs:attributeGroup ref="attrs"/>
          <xs:attribute name="cite" type="URI"/>
          <xs:attribute name="datetime" type="Datetime"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="del">
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Flow">
          <xs:attributeGroup ref="attrs"/>
          <xs:attribute name="cite" type="URI"/>
          <xs:attribute name="datetime" type="Datetime"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    ================== The Anchor Element ================================
    </xs:documentation>
  </xs:annotation>

  <xs:element name="a">
    <xs:annotation>
      <xs:documentation>
      content is "Inline" except that anchors shouldn't be nested
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="a.content">
          <xs:attributeGroup ref="attrs"/>
          <xs:attributeGroup ref="focus"/>
          <xs:attribute name="charset" type="Charset"/>
          <xs:attribute name="type" type="ContentType"/>
          <xs:attribute name="name" type="xs:NMTOKEN"/>
          <xs:attribute name="href" type="URI"/>
          <xs:attribute name="hreflang" type="LanguageCode"/>
          <xs:attribute name="rel" type="LinkTypes"/>
          <xs:attribute name="rev" type="LinkTypes"/>
          <xs:attribute name="shape" default="rect" type="Shape"/>
          <xs:attribute name="coords" type="Coords"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    ===================== Inline Elements ================================
    </xs:documentation>
  </xs:annotation>

  <xs:element name="span">
    <xs:annotation>
      <xs:documentation>
      generic language/style container
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="bdo">
    <xs:annotation>
      <xs:documentation>
      I18N BiDi over-ride
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="coreattrs"/>
          <xs:attributeGroup ref="events"/>
          <xs:attribute name="lang" type="LanguageCode"/>
          <xs:attribute ref="xml:lang"/>
          <xs:attribute name="dir" use="required">
            <xs:simpleType>
              <xs:restriction base="xs:token">
                <xs:enumeration value="ltr"/>
                <xs:enumeration value="rtl"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:attribute>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="br">
    <xs:annotation>
      <xs:documentation>
      forced line break
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attributeGroup ref="coreattrs"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="em">
    <xs:annotation>
      <xs:documentation>
      emphasis
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="strong">
    <xs:annotation>
      <xs:documentation>
      strong emphasis
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="dfn">
    <xs:annotation>
      <xs:documentation>
      definitional
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="code">
    <xs:annotation>
      <xs:documentation>
      program code
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="samp">
    <xs:annotation>
      <xs:documentation>
      sample
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="kbd">
    <xs:annotation>
      <xs:documentation>
      something user would type
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="var">
    <xs:annotation>
      <xs:documentation>
      variable
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="cite">
    <xs:annotation>
      <xs:documentation>
      citation
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="abbr">
    <xs:annotation>
      <xs:documentation>
      abbreviation
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="acronym">
    <xs:annotation>
      <xs:documentation>
      acronym
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="q">
    <xs:annotation>
      <xs:documentation>
      inlined quote
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
          <xs:attribute name="cite" type="URI"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="sub">
    <xs:annotation>
      <xs:documentation>
      subscript
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="sup">
    <xs:annotation>
      <xs:documentation>
      superscript
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="tt">
    <xs:annotation>
      <xs:documentation>
      fixed pitch font
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="i">
    <xs:annotation>
      <xs:documentation>
      italic font
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="b">
    <xs:annotation>
      <xs:documentation>
      bold font
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="big">
    <xs:annotation>
      <xs:documentation>
      bigger font
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="small">
    <xs:annotation>
      <xs:documentation>
      smaller font
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    ==================== Object ======================================

    object is used to embed objects as part of HTML pages.
    param elements should precede other content. Parameters
    can also be expressed as attribute/value pairs on the
    object element itself when brevity is desired.
    </xs:documentation>
  </xs:annotation>

  <xs:element name="object">
    <xs:complexType mixed="true">
      <xs:choice minOccurs="0" maxOccurs="unbounded">
        <xs:element ref="param"/>
        <xs:group ref="block"/>
        <xs:element ref="form"/>
        <xs:group ref="inline"/>
        <xs:group ref="misc"/>
      </xs:choice>
      <xs:attributeGroup ref="attrs"/>
      <xs:attribute name="declare">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="declare"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="classid" type="URI"/>
      <xs:attribute name="codebase" type="URI"/>
      <xs:attribute name="data" type="URI"/>
      <xs:attribute name="type" type="ContentType"/>
      <xs:attribute name="codetype" type="ContentType"/>
      <xs:attribute name="archive" type="UriList"/>
      <xs:attribute name="standby" type="Text"/>
      <xs:attribute name="height" type="Length"/>
      <xs:attribute name="width" type="Length"/>
      <xs:attribute name="usemap" type="URI"/>
      <xs:attribute name="name" type="xs:NMTOKEN"/>
      <xs:attribute name="tabindex" type="tabindexNumber"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="param">
    <xs:annotation>
      <xs:documentation>
      param is used to supply a named property value.
      In XML it would seem natural to follow RDF and support an
      abbreviated syntax where the param elements are replaced
      by attribute value pairs on the object start tag.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attribute name="id" type="xs:ID"/>
      <xs:attribute name="name"/>
      <xs:attribute name="value"/>
      <xs:attribute name="valuetype" default="data">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="data"/>
            <xs:enumeration value="ref"/>
            <xs:enumeration value="object"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="type" type="ContentType"/>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    =================== Images ===========================================

    To avoid accessibility problems for people who aren't
    able to see the image, you should provide a text
    description using the alt and longdesc attributes.
    In addition, avoid the use of server-side image maps.
    Note that in this DTD there is no name attribute. That
    is only available in the transitional and frameset DTD.
    </xs:documentation>
  </xs:annotation>

  <xs:element name="img">
    <xs:complexType>
      <xs:attributeGroup ref="attrs"/>
      <xs:attribute name="src" use="required" type="URI"/>
      <xs:attribute name="alt" use="required" type="Text"/>
      <xs:attribute name="longdesc" type="URI"/>
      <xs:attribute name="height" type="Length"/>
      <xs:attribute name="width" type="Length"/>
      <xs:attribute name="usemap" type="URI">
	<xs:annotation>
	  <xs:documentation>
          usemap points to a map element which may be in this document
          or an external document, although the latter is not widely supported
          </xs:documentation>
	</xs:annotation>
      </xs:attribute>
      <xs:attribute name="ismap">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="ismap"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    ================== Client-side image maps ============================

    These can be placed in the same document or grouped in a
    separate document although this isn't yet widely supported
    </xs:documentation>
  </xs:annotation>

  <xs:element name="map">
    <xs:complexType>
      <xs:choice>
        <xs:choice maxOccurs="unbounded">
          <xs:group ref="block"/>
          <xs:element ref="form"/>
          <xs:group ref="misc"/>
        </xs:choice>
        <xs:element maxOccurs="unbounded" ref="area"/>
      </xs:choice>
      <xs:attributeGroup ref="i18n"/>
      <xs:attributeGroup ref="events"/>
      <xs:attribute name="id" use="required" type="xs:ID"/>
      <xs:attribute name="class"/>
      <xs:attribute name="style" type="StyleSheet"/>
      <xs:attribute name="title" type="Text"/>
      <xs:attribute name="name" type="xs:NMTOKEN"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="area">
    <xs:complexType>
        <xs:attributeGroup ref="attrs"/>
      <xs:attributeGroup ref="focus"/>
      <xs:attribute name="shape" default="rect" type="Shape"/>
      <xs:attribute name="coords" type="Coords"/>
      <xs:attribute name="href" type="URI"/>
      <xs:attribute name="nohref">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="nohref"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="alt" use="required" type="Text"/>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    ================ Forms ===============================================
    </xs:documentation>
  </xs:annotation>

  <xs:element name="form">
    <xs:complexType>
      <xs:complexContent>
        <xs:extension base="form.content">
          <xs:attributeGroup ref="attrs"/>
          <xs:attribute name="action" use="required" type="URI"/>
          <xs:attribute name="method" default="get">
            <xs:simpleType>
              <xs:restriction base="xs:token">
                <xs:enumeration value="get"/>
                <xs:enumeration value="post"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:attribute>
          <xs:attribute name="enctype" type="ContentType"
              default="application/x-www-form-urlencoded"/>
          <xs:attribute name="onsubmit" type="Script"/>
          <xs:attribute name="onreset" type="Script"/>
          <xs:attribute name="accept" type="ContentTypes"/>
          <xs:attribute name="accept-charset" type="Charsets"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="label">
    <xs:annotation>
      <xs:documentation>
      Each label must not contain more than ONE field
      Label elements shouldn't be nested.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
          <xs:attribute name="for" type="xs:IDREF"/>
          <xs:attribute name="accesskey" type="Character"/>
          <xs:attribute name="onfocus" type="Script"/>
          <xs:attribute name="onblur" type="Script"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:simpleType name="InputType">
    <xs:restriction base="xs:token">
      <xs:enumeration value="text"/>
      <xs:enumeration value="password"/>
      <xs:enumeration value="checkbox"/>
      <xs:enumeration value="radio"/>
      <xs:enumeration value="submit"/>
      <xs:enumeration value="reset"/>
      <xs:enumeration value="file"/>
      <xs:enumeration value="hidden"/>
      <xs:enumeration value="image"/>
      <xs:enumeration value="button"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:element name="input">
    <xs:annotation>
      <xs:documentation>
      form control
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attributeGroup ref="attrs"/>
      <xs:attributeGroup ref="focus"/>
      <xs:attribute name="type" default="text" type="InputType"/>
      <xs:attribute name="name">
	<xs:annotation>
	  <xs:documentation>
          the name attribute is required for all but submit & reset
          </xs:documentation>
	</xs:annotation>
      </xs:attribute>
      <xs:attribute name="value"/>
      <xs:attribute name="checked">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="checked"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="disabled">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="disabled"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="readonly">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="readonly"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="size"/>
      <xs:attribute name="maxlength" type="Number"/>
      <xs:attribute name="src" type="URI"/>
      <xs:attribute name="alt"/>
      <xs:attribute name="usemap" type="URI"/>
      <xs:attribute name="onselect" type="Script"/>
      <xs:attribute name="onchange" type="Script"/>
      <xs:attribute name="accept" type="ContentTypes"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="select">
    <xs:annotation>
      <xs:documentation>
      option selector
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:choice maxOccurs="unbounded">
        <xs:element ref="optgroup"/>
        <xs:element ref="option"/>
      </xs:choice>
      <xs:attributeGroup ref="attrs"/>
      <xs:attribute name="name"/>
      <xs:attribute name="size" type="Number"/>
      <xs:attribute name="multiple">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="multiple"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="disabled">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="disabled"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="tabindex" type="tabindexNumber"/>
      <xs:attribute name="onfocus" type="Script"/>
      <xs:attribute name="onblur" type="Script"/>
      <xs:attribute name="onchange" type="Script"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="optgroup">
    <xs:annotation>
      <xs:documentation>
      option group
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" ref="option"/>
      </xs:sequence>
      <xs:attributeGroup ref="attrs"/>
      <xs:attribute name="disabled">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="disabled"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="label" use="required" type="Text"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="option">
    <xs:annotation>
      <xs:documentation>
      selectable choice
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:attributeGroup ref="attrs"/>
      <xs:attribute name="selected">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="selected"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="disabled">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="disabled"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="label" type="Text"/>
      <xs:attribute name="value"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="textarea">
    <xs:annotation>
      <xs:documentation>
      multi-line text field
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:attributeGroup ref="attrs"/>
      <xs:attributeGroup ref="focus"/>
      <xs:attribute name="name"/>
      <xs:attribute name="rows" use="required" type="Number"/>
      <xs:attribute name="cols" use="required" type="Number"/>
      <xs:attribute name="disabled">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="disabled"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="readonly">
        <xs:simpleType>
          <xs:restriction base="xs:token">
            <xs:enumeration value="readonly"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
      <xs:attribute name="onselect" type="Script"/>
      <xs:attribute name="onchange" type="Script"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="fieldset">
    <xs:annotation>
      <xs:documentation>
      The fieldset element is used to group form fields.
      Only one legend element should occur in the content
      and if present should only be preceded by whitespace.

      NOTE: this content model is different from the XHTML 1.0 DTD,
      closer to the intended content model in HTML4 DTD
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:sequence>
        <xs:element ref="legend"/>
        <xs:choice minOccurs="0" maxOccurs="unbounded">
          <xs:group ref="block"/>
          <xs:element ref="form"/>
          <xs:group ref="inline"/>
          <xs:group ref="misc"/>
        </xs:choice>
      </xs:sequence>
      <xs:attributeGroup ref="attrs"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="legend">
    <xs:annotation>
      <xs:documentation>
      fieldset label
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
          <xs:attribute name="accesskey" type="Character"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="button">
    <xs:annotation>
      <xs:documentation>
      Content is "Flow" excluding a, form and form controls
      </xs:documentation>
    </xs:annotation>
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="button.content">
          <xs:attributeGroup ref="attrs"/>
          <xs:attributeGroup ref="focus"/>
          <xs:attribute name="name"/>
          <xs:attribute name="value"/>
          <xs:attribute name="type" default="submit">
            <xs:simpleType>
              <xs:restriction base="xs:token">
                <xs:enumeration value="button"/>
                <xs:enumeration value="submit"/>
                <xs:enumeration value="reset"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:attribute>
          <xs:attribute name="disabled">
            <xs:simpleType>
              <xs:restriction base="xs:token">
                <xs:enumeration value="disabled"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:attribute>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    ======================= Tables =======================================

    Derived from IETF HTML table standard, see [RFC1942]
    </xs:documentation>
  </xs:annotation>

  <xs:simpleType name="TFrame">
    <xs:annotation>
      <xs:documentation>
      The border attribute sets the thickness of the frame around the
      table. The default units are screen pixels.

      The frame attribute specifies which parts of the frame around
      the table should be rendered. The values are not the same as
      CALS to avoid a name clash with the valign attribute.
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:token">
      <xs:enumeration value="void"/>
      <xs:enumeration value="above"/>
      <xs:enumeration value="below"/>
      <xs:enumeration value="hsides"/>
      <xs:enumeration value="lhs"/>
      <xs:enumeration value="rhs"/>
      <xs:enumeration value="vsides"/>
      <xs:enumeration value="box"/>
      <xs:enumeration value="border"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="TRules">
    <xs:annotation>
      <xs:documentation>
      The rules attribute defines which rules to draw between cells:

      If rules is absent then assume:
          "none" if border is absent or border="0" otherwise "all"
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:token">
      <xs:enumeration value="none"/>
      <xs:enumeration value="groups"/>
      <xs:enumeration value="rows"/>
      <xs:enumeration value="cols"/>
      <xs:enumeration value="all"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:attributeGroup name="cellhalign">
    <xs:annotation>
      <xs:documentation>
      horizontal alignment attributes for cell contents

      char        alignment char, e.g. char=':'
      charoff     offset for alignment char
      </xs:documentation>
    </xs:annotation>
    <xs:attribute name="align">
      <xs:simpleType>
        <xs:restriction base="xs:token">
          <xs:enumeration value="left"/>
          <xs:enumeration value="center"/>
          <xs:enumeration value="right"/>
          <xs:enumeration value="justify"/>
          <xs:enumeration value="char"/>
        </xs:restriction>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute name="char" type="Character"/>
    <xs:attribute name="charoff" type="Length"/>
  </xs:attributeGroup>

  <xs:attributeGroup name="cellvalign">
    <xs:annotation>
      <xs:documentation>
      vertical alignment attributes for cell contents
      </xs:documentation>
    </xs:annotation>
    <xs:attribute name="valign">
      <xs:simpleType>
        <xs:restriction base="xs:token">
          <xs:enumeration value="top"/>
          <xs:enumeration value="middle"/>
          <xs:enumeration value="bottom"/>
          <xs:enumeration value="baseline"/>
        </xs:restriction>
      </xs:simpleType>
    </xs:attribute>
  </xs:attributeGroup>

  <xs:element name="table">
    <xs:complexType>
      <xs:sequence>
        <xs:element minOccurs="0" ref="caption"/>
        <xs:choice>
          <xs:element minOccurs="0" maxOccurs="unbounded" ref="col"/>
          <xs:element minOccurs="0" maxOccurs="unbounded" ref="colgroup"/>
        </xs:choice>
        <xs:element minOccurs="0" ref="thead"/>
        <xs:element minOccurs="0" ref="tfoot"/>
        <xs:choice>
          <xs:element maxOccurs="unbounded" ref="tbody"/>
          <xs:element maxOccurs="unbounded" ref="tr"/>
        </xs:choice>
      </xs:sequence>
      <xs:attributeGroup ref="attrs"/>
      <xs:attribute name="summary" type="Text"/>
      <xs:attribute name="width" type="Length"/>
      <xs:attribute name="border" type="Pixels"/>
      <xs:attribute name="frame" type="TFrame"/>
      <xs:attribute name="rules" type="TRules"/>
      <xs:attribute name="cellspacing" type="Length"/>
      <xs:attribute name="cellpadding" type="Length"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="caption">
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Inline">
          <xs:attributeGroup ref="attrs"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:annotation>
    <xs:documentation>
    Use thead to duplicate headers when breaking table
    across page boundaries, or for static headers when
    tbody sections are rendered in scrolling panel.

    Use tfoot to duplicate footers when breaking table
    across page boundaries, or for static footers when
    tbody sections are rendered in scrolling panel.

    Use multiple tbody sections when rules are needed
    between groups of table rows.
    </xs:documentation>
  </xs:annotation>

  <xs:element name="thead">
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" ref="tr"/>
      </xs:sequence>
      <xs:attributeGroup ref="attrs"/>
      <xs:attributeGroup ref="cellhalign"/>
      <xs:attributeGroup ref="cellvalign"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="tfoot">
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" ref="tr"/>
      </xs:sequence>
      <xs:attributeGroup ref="attrs"/>
      <xs:attributeGroup ref="cellhalign"/>
      <xs:attributeGroup ref="cellvalign"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="tbody">
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" ref="tr"/>
      </xs:sequence>
      <xs:attributeGroup ref="attrs"/>
      <xs:attributeGroup ref="cellhalign"/>
      <xs:attributeGroup ref="cellvalign"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="colgroup">
    <xs:annotation>
      <xs:documentation>
      colgroup groups a set of col elements. It allows you to group
      several semantically related columns together.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:sequence>
        <xs:element minOccurs="0" maxOccurs="unbounded" ref="col"/>
      </xs:sequence>
      <xs:attributeGroup ref="attrs"/>
      <xs:attribute name="span" default="1" type="Number"/>
      <xs:attribute name="width" type="MultiLength"/>
      <xs:attributeGroup ref="cellhalign"/>
      <xs:attributeGroup ref="cellvalign"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="col">
    <xs:annotation>
      <xs:documentation>
      col elements define the alignment properties for cells in
      one or more columns.

      The width attribute specifies the width of the columns, e.g.

          width=64        width in screen pixels
          width=0.5*      relative width of 0.5

      The span attribute causes the attributes of one
      col element to apply to more than one column.
      </xs:documentation>
    </xs:annotation>
    <xs:complexType>
      <xs:attributeGroup ref="attrs"/>
      <xs:attribute name="span" default="1" type="Number"/>
      <xs:attribute name="width" type="MultiLength"/>
      <xs:attributeGroup ref="cellhalign"/>
      <xs:attributeGroup ref="cellvalign"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="tr">
    <xs:complexType>
      <xs:choice maxOccurs="unbounded">
        <xs:element ref="th"/>
        <xs:element ref="td"/>
      </xs:choice>
      <xs:attributeGroup ref="attrs"/>
      <xs:attributeGroup ref="cellhalign"/>
      <xs:attributeGroup ref="cellvalign"/>
    </xs:complexType>
  </xs:element>

  <xs:simpleType name="Scope">
    <xs:annotation>
      <xs:documentation>
      Scope is simpler than headers attribute for common tables
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:token">
      <xs:enumeration value="row"/>
      <xs:enumeration value="col"/>
      <xs:enumeration value="rowgroup"/>
      <xs:enumeration value="colgroup"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:annotation>
    <xs:documentation>
    th is for headers, td for data and for cells acting as both
    </xs:documentation>
  </xs:annotation>

  <xs:element name="th">
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Flow">
          <xs:attributeGroup ref="attrs"/>
          <xs:attribute name="abbr" type="Text"/>
          <xs:attribute name="axis"/>
          <xs:attribute name="headers" type="xs:IDREFS"/>
          <xs:attribute name="scope" type="Scope"/>
          <xs:attribute name="rowspan" default="1" type="Number"/>
          <xs:attribute name="colspan" default="1" type="Number"/>
          <xs:attributeGroup ref="cellhalign"/>
          <xs:attributeGroup ref="cellvalign"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

  <xs:element name="td">
    <xs:complexType mixed="true">
      <xs:complexContent>
        <xs:extension base="Flow">
          <xs:attributeGroup ref="attrs"/>
          <xs:attribute name="abbr" type="Text"/>
          <xs:attribute name="axis"/>
          <xs:attribute name="headers" type="xs:IDREFS"/>
          <xs:attribute name="scope" type="Scope"/>
          <xs:attribute name="rowspan" default="1" type="Number"/>
          <xs:attribute name="colspan" default="1" type="Number"/>
          <xs:attributeGroup ref="cellhalign"/>
          <xs:attributeGroup ref="cellvalign"/>
        </xs:extension>
      </xs:complexContent>
    </xs:complexType>
  </xs:element>

</xs:schema>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/config/xml.xsd
+++ modules/damieng/graphical_editor/loncapa_daxe/web/config/xml.xsd
<?xml version='1.0'?>
<!-- <!DOCTYPE xs:schema PUBLIC "-//W3C//DTD XMLSCHEMA 200102//EN" "XMLSchema.dtd" > -->
<xs:schema targetNamespace="http://www.w3.org/XML/1998/namespace" xmlns:xs="http://www.w3.org/2001/XMLSchema" xml:lang="en">

 <xs:annotation>
  <xs:documentation>
   See http://www.w3.org/XML/1998/namespace.html and
   http://www.w3.org/TR/REC-xml for information about this namespace.

    This schema document describes the XML namespace, in a form
    suitable for import by other schema documents.  

    Note that local names in this namespace are intended to be defined
    only by the World Wide Web Consortium or its subgroups.  The
    following names are currently defined in this namespace and should
    not be used with conflicting semantics by any Working Group,
    specification, or document instance:

    base (as an attribute name): denotes an attribute whose value
         provides a URI to be used as the base for interpreting any
         relative URIs in the scope of the element on which it
         appears; its value is inherited.  This name is reserved
         by virtue of its definition in the XML Base specification.

    id   (as an attribute name): denotes an attribute whose value
         should be interpreted as if declared to be of type ID.
         The xml:id specification is not yet a W3C Recommendation,
         but this attribute is included here to facilitate experimentation
         with the mechanisms it proposes.  Note that it is _not_ included
         in the specialAttrs attribute group.

    lang (as an attribute name): denotes an attribute whose value
         is a language code for the natural language of the content of
         any element; its value is inherited.  This name is reserved
         by virtue of its definition in the XML specification.
  
    space (as an attribute name): denotes an attribute whose
         value is a keyword indicating what whitespace processing
         discipline is intended for the content of the element; its
         value is inherited.  This name is reserved by virtue of its
         definition in the XML specification.

    Father (in any context at all): denotes Jon Bosak, the chair of 
         the original XML Working Group.  This name is reserved by 
         the following decision of the W3C XML Plenary and 
         XML Coordination groups:

             In appreciation for his vision, leadership and dedication
             the W3C XML Plenary on this 10th day of February, 2000
             reserves for Jon Bosak in perpetuity the XML name
             xml:Father
  </xs:documentation>
 </xs:annotation>

 <xs:annotation>
  <xs:documentation>This schema defines attributes and an attribute group
        suitable for use by
        schemas wishing to allow xml:base, xml:lang or xml:space attributes
        on elements they define.

        To enable this, such a schema must import this schema
        for the XML namespace, e.g. as follows:
        <schema . . .>
         . . .
         <import namespace="http://www.w3.org/XML/1998/namespace"
                    schemaLocation="http://www.w3.org/2001/03/xml.xsd"/>

        Subsequently, qualified reference to any of the attributes
        or the group defined below will have the desired effect, e.g.

        <type . . .>
         . . .
         <attributeGroup ref="xml:specialAttrs"/>
 
         will define a type which will schema-validate an instance
         element with any of those attributes</xs:documentation>
 </xs:annotation>

 <xs:annotation>
  <xs:documentation>In keeping with the XML Schema WG's standard versioning
   policy, this schema document will persist at
   http://www.w3.org/2004/10/xml.xsd.
   At the date of issue it can also be found at
   http://www.w3.org/2001/xml.xsd.
   The schema document at that URI may however change in the future,
   in order to remain compatible with the latest version of XML Schema
   itself, or with the XML namespace itself.  In other words, if the XML
   Schema or XML namespaces change, the version of this document at
   http://www.w3.org/2001/xml.xsd will change
   accordingly; the version at
   http://www.w3.org/2004/10/xml.xsd will not change.
  </xs:documentation>
 </xs:annotation>

 <xs:attribute name="lang" type="xs:language">
  <xs:annotation>
   <xs:documentation>Attempting to install the relevant ISO 2- and 3-letter
         codes as the enumerated possible values is probably never
         going to be a realistic possibility.  See
         RFC 3066 at http://www.ietf.org/rfc/rfc3066.txt and the IANA registry
         at http://www.iana.org/assignments/lang-tag-apps.htm for
         further information.</xs:documentation>
  </xs:annotation>
 </xs:attribute>

 <xs:attribute name="space">
  <xs:simpleType>
   <xs:restriction base="xs:NCName">
    <xs:enumeration value="default"/>
    <xs:enumeration value="preserve"/>
   </xs:restriction>
  </xs:simpleType>
 </xs:attribute>

 <xs:attribute name="base" type="xs:anyURI">
  <xs:annotation>
   <xs:documentation>See http://www.w3.org/TR/xmlbase/ for
                     information about this attribute.</xs:documentation>
  </xs:annotation>
 </xs:attribute>
 
 <xs:attribute name="id" type="xs:ID">
  <xs:annotation>
   <xs:documentation>See http://www.w3.org/TR/xml-id/ for
                     information about this attribute.</xs:documentation>
  </xs:annotation>
 </xs:attribute>

 <xs:attributeGroup name="specialAttrs">
  <xs:attribute ref="xml:base"/>
  <xs:attribute ref="xml:lang"/>
  <xs:attribute ref="xml:space"/>
 </xs:attributeGroup>

</xs:schema>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/nodes/hintgroup.dart
+++ modules/damieng/graphical_editor/loncapa_daxe/web/nodes/hintgroup.dart
/*
  This file is part of LONCAPA-Daxe.

  LONCAPA-Daxe 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 3 of the License, or
  (at your option) any later version.

  LONCAPA-Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of loncapa_daxe;

/**
 * This is used by RadioResponse for simple UI.
 */
class Hintgroup extends LCDBlock {
  
  Hintgroup.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
  }
  
  Hintgroup.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
    if (parent is LCDBlock && (parent as LCDBlock).simpleUI)
      simpleUI = true;
  }
  
  /**
   * Returns true if the given node can be displayed with a simple UI.
   */
  bool simpleUIPossible() {
    if (attributes.length != 0)
      return false;
    // check if there is a single hintpart with on="default" and simple content inside
    bool hintpartok = false;
    bool othernode = false;
    for (DaxeNode dn in childNodes) {
      if (dn.nodeType == DaxeNode.ELEMENT_NODE && dn.nodeName == 'hintpart' &&
          dn.getAttribute('on') == 'default' && dn.attributes.length == 1 &&
          SimpleUIText.checkNodeContents(dn))
        hintpartok = true;
      else if (dn.nodeType != DaxeNode.TEXT_NODE || dn.nodeValue.trim() != '')
        othernode = true;
    }
    if (hintpartok && !othernode)
      return true;
    // otherwise just check the contents
    return SimpleUIText.checkNodeContents(this);
  }
  
  void fixStructureForSimpleUI() {
    // remove hintpart (but keep its content)
    for (DaxeNode dn in childNodes) {
      if (dn.nodeType == DaxeNode.ELEMENT_NODE && dn.nodeName == 'hintpart') {
        removeChild(dn);
        // remove spaces inside this node
        normalize();
        if (firstChild is DNText && firstChild.nodeValue.trim() == '' &&
            firstChild.nextSibling == null)
          removeChild(firstChild);
        // remove left and right spaces inside hintpart
        if (dn.firstChild.nodeType == DaxeNode.TEXT_NODE) {
          if (dn.firstChild.nodeValue.trim() == '')
            dn.removeChild(dn.firstChild);
          else
            dn.firstChild.nodeValue = dn.firstChild.nodeValue.trimLeft();
        }
        if (dn.lastChild.nodeType == DaxeNode.TEXT_NODE) {
          if (dn.lastChild.nodeValue.trim() == '')
            dn.removeChild(dn.lastChild);
          else
            dn.lastChild.nodeValue = dn.lastChild.nodeValue.trimRight();
        }
        // append hintpart content
        for (DaxeNode dn2=dn.firstChild; dn2!=null; dn2=dn.firstChild) {
          dn.removeChild(dn2);
          appendChild(dn2);
        }
        break;
      }
    }
  }
  
  @override
  h.Element html() {
    simpleUI = parent is LCDBlock && (parent as LCDBlock).simpleUI;
    if (!simpleUI)
      return super.html();
    h.DivElement div = new h.DivElement();
    div.id = id;
    h.SpanElement titleSpan = new h.SpanElement();
    titleSpan.appendText(LCDStrings.get('hint') + ' ');
    div.append(titleSpan);
    h.SpanElement contentsSpan = new h.SpanElement();
    for (DaxeNode dn=firstChild; dn!= null; dn=dn.nextSibling) {
      contentsSpan.append(dn.html());
    }
    div.append(contentsSpan);
    return div;
  }
  
  @override
  h.Element getHTMLContentsNode() {
    if (!simpleUI)
      return super.getHTMLContentsNode();
    return(getHTMLNode().nodes[1]);
  }
  
}

Index: modules/damieng/graphical_editor/loncapa_daxe/web/nodes/lcd_block.dart
+++ modules/damieng/graphical_editor/loncapa_daxe/web/nodes/lcd_block.dart
/*
  This file is part of LONCAPA-Daxe.

  LONCAPA-Daxe 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 3 of the License, or
  (at your option) any later version.

  LONCAPA-Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of loncapa_daxe;

/**
 * Generic block element for LON-CAPA
 * Attributes can be edited directly in it.
 * Buttons can be used to collapse the element and the attribute fields.
 * Jaxe display type: 'lcdblock'.
 */
class LCDBlock extends DaxeNode {
  
  List<x.Element> attRefs;
  int state; // 0 = editable attributes, 1 = non-editable attributes, 2 = collapsed element
  HashMap<String, SimpleTypeControl> attributeControls;
  HashMap<DaxeAttr, h.TextInputElement> unknownAttributeFields;
  bool hasContent;
  LCDButton bEditable, bNormal, bCollapsed;
  bool simpleUI = false;
  bool displaySimpleButton = false;
  
  
  LCDBlock.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
    state = 0;
    init();
  }
  
  LCDBlock.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
    // replace CDATA sections by text
    DaxeNode child = firstChild;
    while (child != null) {
      DaxeNode next = child.nextSibling;
      if (child is DNCData) {
        if (child.previousSibling is DNText && child.previousSibling.nodeValue == '\n')
          removeChild(child.previousSibling);
        if (child.nextSibling is DNText && child.nextSibling.nodeValue == '\n') {
          next = child.nextSibling.nextSibling;
          removeChild(child.nextSibling);
        }
        String text;
        if (child.firstChild != null)
          text = child.firstChild.nodeValue;
        else
          text = null;
        if (text != null)
          insertBefore(new DNText(text), child);
        removeChild(child);
      }
      child = next;
    }
    normalize();
    state = 1;
    init();
    fixLineBreaks();
  }
  
  void init() {
    attRefs = doc.cfg.elementAttributes(ref);
    if (attRefs != null && attRefs.length > 0)
      attributeControls = new HashMap<String, SimpleTypeControl>();
    unknownAttributeFields = null;
    hasContent = doc.cfg.canContainText(ref) || doc.cfg.subElements(ref).length > 0;
  }
  
  /**
   * Returns true if the node can be displayed with a simple UI.
   * This is replaced by subclasses.
   */
  bool simpleUIPossible() {
    return false;
  }
  
  /**
   * Called when switching to simple UI.
   * This is replaced by subclasses.
   */
  void setupSimpleUI() {
  }
  
  void switchToAdvancedUI() {
    simpleUI = false;
    init();
    state = 1;
    restrictedInserts = null;
    updateHTML();
  }
  
  @override
  h.Element html() {
    h.DivElement div = new h.DivElement();
    div.id = "$id";
    div.classes.add('dn');
    if (!valid)
      div.classes.add('invalid');
    div.classes.add('lcdblock');
    h.DivElement headerDiv = new h.DivElement();
    headerDiv.classes.add('lcdblock-header');
    if (state == 2 || !hasContent)
      headerDiv.classes.add('without-content-afterwards');
    
    h.DivElement titleDiv = new h.DivElement();
    h.DivElement buttonBox = new h.DivElement();
    buttonBox.classes.add('lcd-button-box');
    bCollapsed = new LCDButton(LCDStrings.get('lcdblock_collapsed'), 'block_collapsed.png',
        () {
      collapsedView();
    }, selected: (state == 2));
    buttonBox.append(bCollapsed.html());
    bNormal = new LCDButton(LCDStrings.get('lcdblock_normal'), 'block_normal.png',
        () {
      normalView();
    }, selected: (state == 1));
    buttonBox.append(bNormal.html());
    bEditable = new LCDButton(LCDStrings.get('lcdblock_editable'), 'block_editable.png',
        () {
      editableView();
    }, enabled: attRefs != null && attRefs.length > 0, selected: (state == 0));
    buttonBox.append(bEditable.html());
    titleDiv.append(buttonBox);
    
    if (simpleUI) {
      h.ButtonElement advanced = new h.ButtonElement();
      advanced.classes.add('lcd-advanced');
      advanced.appendText(LCDStrings.get('advanced'));
      advanced.onClick.listen((h.MouseEvent event) {
        switchToAdvancedUI();
        page.updateAfterPathChange();
      });
      titleDiv.append(advanced);
    } else if (displaySimpleButton) {
      h.ButtonElement simple = new h.ButtonElement();
      simple.classes.add('lcd-advanced');
      simple.appendText(LCDStrings.get('simple'));
      simple.onClick.listen((h.MouseEvent event) {
        if (simpleUIPossible()) {
          simpleUI = true;
          setupSimpleUI();
          updateHTML();
          page.updateAfterPathChange();
        }
      });
      titleDiv.append(simple);
    }
    
    h.SpanElement titleSpan = new h.SpanElement();
    titleSpan.classes.add('lcdblock-title');
    String title = doc.cfg.elementTitle(ref);
    if (title == null)
      title = nodeName;
    titleSpan.append(new h.Text(title));
    titleSpan.onDoubleClick.listen((h.MouseEvent event) {
      page.selectNode(this);
    });
    titleDiv.append(titleSpan);
    
    titleDiv.append(makeHelpButton(ref, null));
    headerDiv.append(titleDiv);
    
    if (state == 0) {
      h.TableElement table = new h.TableElement();
      table.classes.add('expand');
      for (x.Element refAttr in attRefs)
        table.append(attributeHTML(refAttr));
      for (DaxeAttr att in attributes) {
        bool found = false;
        for (x.Element attref in attRefs) {
          if (att.localName == doc.cfg.attributeName(attref) &&
              att.namespaceURI == doc.cfg.attributeNamespace(attref)) {
            found = true;
            break;
          }
        }
        if (!found) {
          table.append(unknownAttributeHTML(att));
        }
      }
      headerDiv.append(table);
    } else if (state == 1) {
      h.DivElement attDiv = new h.DivElement();
      attDiv.classes.add('lcdblock-attributes');
      for (DaxeAttr att in attributes) {
        attDiv.append(new h.Text(" "));
        h.Element att_name = new h.SpanElement();
        att_name.attributes['class'] = 'attribute_name';
        att_name.text = att.localName;
        attDiv.append(att_name);
        attDiv.append(new h.Text("="));
        h.Element att_val = new h.SpanElement();
        att_val.attributes['class'] = 'attribute_value';
        att_val.text = att.value;
        attDiv.append(att_val);
      }
      attDiv.onClick.listen((h.MouseEvent event) {
        editableView();
      });
      headerDiv.append(attDiv);
    }
    div.append(headerDiv);
    if (state != 2 && hasContent) {
      h.DivElement contents = new h.DivElement();
      contents.id = 'contents-' + id;
      contents.classes.add('indent');
      contents.classes.add('lcdblock-content');
      setStyle(contents);
      DaxeNode dn = firstChild;
      while (dn != null) {
        contents.append(dn.html());
        dn = dn.nextSibling;
      }
      if (lastChild == null || lastChild.nodeType == DaxeNode.TEXT_NODE)
        contents.appendText('\n');
      //this kind of conditional HTML makes it hard to optimize display updates:
      //we have to override updateHTMLAfterChildrenChange
      // also, it seems that in IE this adds a BR instead of a text node !
      div.append(contents);
    }
    return(div);
  }
  
  @override
  void updateHTMLAfterChildrenChange(List<DaxeNode> changed) {
    if (simpleUI && !simpleUIPossible()) {
      // an undo could have re-added an advanced child
      switchToAdvancedUI();
    } else
      super.updateHTMLAfterChildrenChange(changed);
    if (!simpleUI && hasContent && state != 2) {
      h.DivElement contents = getHTMLContentsNode();
      if (contents.nodes.length > 0) {
        h.Node hn = contents.nodes.first;
        while (hn != null) {
          h.Node next = hn.nextNode;
          if (hn is h.Text || hn is h.BRElement)
            hn.remove();
          hn = next;
        }
      }
      if (lastChild == null || lastChild.nodeType == DaxeNode.TEXT_NODE)
        contents.appendText('\n');
    }
  }
  
  @override
  void updateAttributes() {
    if (state == 0) {
      h.DivElement div = getHTMLNode();
      h.TableElement table = h.querySelector("#$id>table");
      for (x.Element refAttr in attRefs) {
        String name = doc.cfg.attributeQualifiedName(ref, refAttr);
        String value = getAttribute(name);
        String defaultValue = doc.cfg.defaultAttributeValue(refAttr);
        if (value == null) {
          if (defaultValue != null)
            value = defaultValue;
          else
            value = '';
        }
        SimpleTypeControl control = attributeControls[name];
        control.setValue(value);
        control.checkValue(false);
      }
      updateValidity();
    }
  }
  
  @override
  h.Element getHTMLContentsNode() {
    if (state == 2 || !hasContent)
      return(null);
    return(h.document.getElementById('contents-' + id));
  }
  
  @override
  bool newlineAfter() {
    return(true);
  }
  
  @override
  bool newlineInside() {
    return(hasContent);
  }
  
  @override
  void newNodeCreationUI(ActionFunction okfct) {
    // do not display an attribute dialog when the node is created
    okfct();
  }
  
  @override
  void attributeDialog([ActionFunction okfct]) {
    // this is called by the contextual menu
    state = 0;
    if (getHTMLContentsNode() != null)
      updateHTML();
    if (okfct != null)
      okfct();
  }
  
  @override
  Position firstCursorPositionInside() {
    if (hasContent)
      return(new Position(this, 0));
    else
      return(null);
  }
  
  @override
  Position lastCursorPositionInside() {
    if (hasContent)
      return(new Position(this, offsetLength));
    else
      return(null);
  }
  
  void editableView() {
    bEditable.select();
    bNormal.deselect();
    bCollapsed.deselect();
    state = 0;
    updateHTML();
    if (attRefs.length > 0) {
      String firstAttName = doc.cfg.attributeQualifiedName(ref, attRefs.first);
      attributeControls[firstAttName].focus();
    }
  }
  
  void normalView() {
    bEditable.deselect();
    bNormal.select();
    bCollapsed.deselect();
    state = 1;
    updateHTML();
  }
  
  void collapsedView() {
    bEditable.deselect();
    bNormal.deselect();
    bCollapsed.select();
    state = 2;
    updateHTML();
  }
  
  h.TableRowElement attributeHTML(x.Element refAttr) {
    String name = doc.cfg.attributeQualifiedName(ref, refAttr);
    h.TableRowElement tr = new h.TableRowElement();
    
    h.TableCellElement td = new h.TableCellElement();
    td.classes.add('shrink');
    if (doc.cfg.attributeDocumentation(ref, refAttr) != null) {
      h.ButtonElement bHelp = makeHelpButton(ref, refAttr);
      td.append(bHelp);
    }
    tr.append(td);
    
    td = new h.TableCellElement();
    td.classes.add('shrink');
    td.text = doc.cfg.attributeTitle(ref, refAttr);
    if (doc.cfg.requiredAttribute(ref, refAttr))
      td.classes.add('required');
    else
      td.classes.add('optional');
    tr.append(td);
    
    td = new h.TableCellElement();
    td.classes.add('expand');
    String value = getAttribute(name);
    String defaultValue = doc.cfg.defaultAttributeValue(refAttr);
    if (value == null) {
      if (defaultValue != null)
        value = defaultValue;
      else
        value = '';
    }
    SimpleTypeControl attributeControl;
    attributeControl = new SimpleTypeControl.forAttribute(ref, refAttr, value,
        valueChanged: () => changeAttributeValue(refAttr, attributeControl), catchUndo: true);
    attributeControls[name] = attributeControl;
    h.Element ht = attributeControl.html();
    if (ht.firstChild is h.TextInputElement)
      (ht.firstChild as h.TextInputElement).classes.add('form_field');
    else if (ht.firstChild is h.DataListElement && ht.firstChild.nextNode is h.TextInputElement)
      (ht.firstChild.nextNode as h.TextInputElement).classes.add('form_field');
    td.append(ht);
    tr.append(td);
    
    td = new h.TableCellElement();
    td.classes.add('shrink');
    tr.append(td);
    
    return(tr);
  }
  
  h.TableRowElement unknownAttributeHTML(DaxeAttr att) {
    if (unknownAttributeFields == null)
      unknownAttributeFields = new HashMap<DaxeAttr, h.TextInputElement>();
    h.TextInputElement input = new h.TextInputElement();
    input.spellcheck = false;
    input.size = 40;
    input.value = att.value;
    input.classes.add('invalid');
    input.onInput.listen((h.Event event) => changeUnknownAttributeValue(att, input)); // onInput doesn't work with IE9 and backspace
    input.onKeyUp.listen((h.KeyboardEvent event) => changeUnknownAttributeValue(att, input)); // so we use onKeyUp too
    unknownAttributeFields[att] = input;
    
    h.TableRowElement tr = new h.TableRowElement();
    h.TableCellElement td = new h.TableCellElement();
    td.classes.add('shrink');
    tr.append(td);
    
    td = new h.TableCellElement();
    td.classes.add('shrink');
    td.appendText(att.name);
    tr.append(td);
    
    td = new h.TableCellElement();
    td.classes.add('expand');
    td.append(input);
    tr.append(td);
    
    td = new h.TableCellElement();
    td.classes.add('shrink');
    tr.append(td);
    
    return(tr);
  }
  
  void changeAttributeValue(x.Element refAttr, SimpleTypeControl attributeControl) {
    String value = attributeControl.getValue();
    String name = doc.cfg.attributeQualifiedName(ref, refAttr);
    String defaultValue = doc.cfg.defaultAttributeValue(refAttr);
    DaxeAttr attr;
    if ((value == '' && defaultValue == null) || value == defaultValue)
      attr = new DaxeAttr(name, null); // remove the attribute
    else if (value != '' || defaultValue != null)
      attr = new DaxeAttr(name, value);
    else
      attr = null;
    if (attr != null)
      doc.doNewEdit(new UndoableEdit.changeAttribute(this, attr, updateDisplay: false));
    updateValidity();
  }
  
  void changeUnknownAttributeValue(DaxeAttr att, h.TextInputElement input) {
    String value = input.value;
    if (getAttributeNS(att.namespaceURI, att.localName) != value) {
      String name = att.name;
      DaxeAttr attr;
      if (value == '')
        attr = new DaxeAttr(name, null); // remove the attribute
      else
        attr = new DaxeAttr(name, value);
      doc.doNewEdit(new UndoableEdit.changeAttribute(this, attr, updateDisplay: false));
      updateValidity();
    }
  }
  
  static h.ButtonElement makeHelpButton(final x.Element elementRef, final x.Element attributeRef) {
    h.ButtonElement bHelp = new h.ButtonElement();
    bHelp.attributes['type'] = 'button';
    bHelp.classes.add('help');
    bHelp.value = '?';
    bHelp.text = '?';
    if (attributeRef == null) {
      String title = doc.cfg.documentation(elementRef);
      if (title != null)
        bHelp.title = title;
      bHelp.onClick.listen((h.Event event) => (new HelpDialog.Element(elementRef)).show());
    } else {
      String title = doc.cfg.attributeDocumentation(elementRef, attributeRef);
      if (title != null)
        bHelp.title = title;
      bHelp.onClick.listen((h.Event event) => (new HelpDialog.Attribute(attributeRef, elementRef)).show());
    }
    return(bHelp);
  }
}

Index: modules/damieng/graphical_editor/loncapa_daxe/web/nodes/lcd_parameter.dart
+++ modules/damieng/graphical_editor/loncapa_daxe/web/nodes/lcd_parameter.dart
/*
  This file is part of LONCAPA-Daxe.

  LONCAPA-Daxe 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 3 of the License, or
  (at your option) any later version.

  LONCAPA-Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of loncapa_daxe;

/**
 * Display for the parameter element, based on LCDBlock
 * Jaxe display type: 'parameter'.
 */
class LCDParameter extends LCDBlock {
  static HashMap<String,HashMap<String, LCParameter>> parameters = null;
  
  LCDParameter.fromRef(x.Element elementRef) : super.fromRef(elementRef);
  
  LCDParameter.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent);
  
  Future<bool> _readParameters() {
    Completer<bool> completer = new Completer<bool>();
    x.DOMParser dp = new x.DOMParser();
    dp.parseFromURL('parameters.xml').then((x.Document xdoc) {
      parameters = new HashMap<String,HashMap<String, LCParameter>>();
      x.Element root = xdoc.documentElement;
      for (x.Element context in _getChildrenWithName(root, 'context')) {
        String ancestor = context.getAttribute('ancestor');
        HashMap<String, LCParameter> params = new HashMap<String, LCParameter>();
        parameters[ancestor] = params;
        for (x.Element parameter in _getChildrenWithName(context, 'parameter')) {
          LCParameter param = new LCParameter();
          for (x.Node n=parameter.firstChild; n!=null; n=n.nextSibling) {
            if (n.nodeType == x.Node.ELEMENT_NODE && n.firstChild != null && n.firstChild.nodeType == x.Node.TEXT_NODE) {
              if (n.nodeName == 'name')
                param.name = n.firstChild.nodeValue;
              else if (n.nodeName == 'type')
                param.type = n.firstChild.nodeValue;
              else if (n.nodeName == 'default')
                param.def = n.firstChild.nodeValue;
              else if (n.nodeName == 'title') {
                String lang = (n as x.Element).getAttribute('lang');
                if (lang != null) {
                  if (param.titles == null)
                    param.titles = new HashMap<String, String>();
                  param.titles[lang] = n.firstChild.nodeValue;
                }
              }
            }
          }
          params[param.name] = param;
        }
      }
      completer.complete(true);
    }).catchError((e) => (e) {
      print(e);
      completer.complete(false);
    });
    return(completer.future);
  }
  
  List<x.Element> _getChildrenWithName(x.Element parent, String name) {
    List<x.Element> l = new List<x.Element>();
    for (x.Node n=parent.firstChild; n!=null; n=n.nextSibling) {
      if (n.nodeType == x.Node.ELEMENT_NODE && n.nodeName == name) {
        l.add(n);
      }
    }
    return l;
  }
  
  @override
  h.Element html() {
    h.Element div = super.html();
    if (state != 0)
      return div;
    h.DivElement headerDiv = div.firstChild;
    h.TableElement table = headerDiv.lastChild;
    h.DivElement templateDiv = new h.DivElement();
    Menu menu = new Menu(LCDStrings.get('template'));
    _addTemplates(menu); // this might be async if the config is read
    h.DivElement menuDiv = page.mbar.createMenuDiv(menu);
    h.DivElement menuButtonDiv = new h.DivElement();
    menuButtonDiv.classes.add('toolbar-menu');
    menuButtonDiv.append(menuDiv);
    templateDiv.append(menuButtonDiv);
    headerDiv.insertBefore(templateDiv, table);
    return div;
  }
  
  void _addTemplates(Menu menu) {
    if (parameters == null) {
      _readParameters().then((bool read) {
        if (!read)
          return;
        updateHTML();
      });
      return;
    }
    HashMap<String, LCParameter> context = _getContext();
    if (context == null)
      return;
    for (LCParameter param in context.values) {
      String title = param.titles[LCDStrings.systemLocale];
      if (title == null)
        title = param.titles['en'];
      menu.add(new MenuItem(title, ()=>chooseTemplate(param)));
    }
  }
  
  HashMap<String, LCParameter> _getContext() {
    for (DaxeNode ancestor=parent; ancestor!=null; ancestor=ancestor.parent) {
      if (parameters[ancestor.nodeName] != null) {
        HashMap<String, LCParameter> context = parameters[ancestor.nodeName];
        return context;
      }
    }
    return null;
  }
  
  void chooseTemplate(LCParameter param) {
    LinkedHashMap<String, DaxeAttr> attmap = getAttributesMapCopy();
    attmap['name'] = new DaxeAttr.NS(null, 'name', param.name);
    if (param.type != null)
      attmap['type'] = new DaxeAttr.NS(null, 'type', param.type);
    else
      attmap.remove('type');
    if (param.def != null)
      attmap['default'] = new DaxeAttr.NS(null, 'default', param.def);
    else
      attmap.remove('default');
    if (param.titles != null) {
      String title = param.titles['en']; // using English if possible for the description attribute
      if (title == null)
        title = param.titles[LCDStrings.systemLocale];
      if (title != null)
        attmap['description'] = new DaxeAttr.NS(null, 'description', title);
      else
        attmap.remove('title');
    }
    attmap.remove('id');
    attmap.remove('display');
    UndoableEdit edit = new UndoableEdit.changeAttributes(this, new List.from(attmap.values));
    doc.doNewEdit(edit);
  }
}

class LCParameter {
  String name;
  String type;
  String def;
  HashMap<String, String> titles;
}

Index: modules/damieng/graphical_editor/loncapa_daxe/web/nodes/lm.dart
+++ modules/damieng/graphical_editor/loncapa_daxe/web/nodes/lm.dart
/*
  This file is part of LONCAPA-Daxe.

  LONCAPA-Daxe 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 3 of the License, or
  (at your option) any later version.

  LONCAPA-Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of loncapa_daxe;

/**
 * Displays lm and dlm elements (LON-CAPA inline and display math).
 * Jaxe display type: 'lm'.
 */
class Lm extends DaxeNode {
  
  static String constants = 'c, pi, e, hbar, amu';
  static js.JsObject parser_symbols;
  static js.JsObject parser_units;
  
  Lm.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
  }
  
  Lm.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
  }
  
  @override
  h.Element html() {
    h.Element el;
    if (nodeName == 'dlm')
      el = new h.DivElement();
    else
      el = new h.SpanElement();
    el.id = "$id";
    el.classes.add('dn');
    el.classes.add('math');
    if (!valid)
      el.classes.add('invalid');
    el.onClick.listen((h.MouseEvent event) => makeEditable());
    updateEquationDisplay(el);
    return(el);
  }
  
  @override
  void newNodeCreationUI(ActionFunction okfct) {
    okfct();
    makeEditable();
  }
  
  @override
  Position firstCursorPositionInside() {
    return(null);
  }
  
  @override
  Position lastCursorPositionInside() {
    return(null);
  }
  
  @override
  void afterInsert() {
    h.Element el = h.document.getElementById(id);
    updateEquationDisplay(el);
  }
  
  void updateEquationDisplay(h.Element el) {
    String equationText = '?';
    if (firstChild != null && firstChild.nodeValue.trim() != '')
      equationText = firstChild.nodeValue;
    js.JsObject parser;
    if (getAttribute('mode') == 'units') {
      if (parser_units == null)
        parser_units = new js.JsObject(js.context['LCMATH']['Parser'], [true, true, constants]);
      parser = parser_units;
    } else {
      if (parser_symbols == null)
        parser_symbols = new js.JsObject(js.context['LCMATH']['Parser'], [true, false, constants]);
      parser = parser_symbols;
    }
    for (h.Node n in el.childNodes)
      n.remove();
    try {
      js.JsObject root = parser.callMethod('parse', [equationText]);
      if (root != null) {
        h.Element math = h.document.createElement('math');
        math.setAttribute('display', nodeName == 'dlm' ? 'block' : 'inline');
        js.JsObject colors = new js.JsObject.jsify(['#000000']); // to use only black
        math.append(root.callMethod('toMathML', [colors]));
        el.append(math);
        Timer.run(() {
          js.JsArray params = new js.JsObject.jsify( ['Typeset', js.context['MathJax']['Hub'], id] );
          js.context['MathJax']['Hub'].callMethod('Queue', [params]);
          js.context['MathJax']['Hub'].callMethod('Queue', [() => page.cursor.refresh()]);
        });
      }
    } catch (e) {
      el.text = 'Error: ' + e.toString();
    }
  }
  
  void makeEditable() {
    h.Element el = h.document.getElementById(id);
    if (el == null)
      return;
    h.Element editEl;
    if (nodeName == 'dlm')
      editEl = new h.DivElement();
    else
      editEl = new h.SpanElement();
    editEl.id = id;
    h.TextInputElement input = new h.TextInputElement();
    input.classes.add('math');
    input.setAttribute('data-unit_mode', getAttribute('mode') == 'units' ? 'true' : 'false');
    input.setAttribute('data-constants', constants);
    input.setAttribute('data-implicit_operators', 'true');
    input.setAttribute('spellcheck', 'false');
    if (firstChild != null) {
      input.value = firstChild.nodeValue;
      if (input.value.length > 20)
        input.size = input.value.length;
    }
    editEl.append(input);
    h.SelectElement select = new h.SelectElement();
    h.OptionElement symbolsOption = new h.OptionElement();
    symbolsOption.value = 'symbols';
    symbolsOption.appendText(LCDStrings.get('lm_symbols'));
    if (getAttribute('mode') != 'units')
      symbolsOption.setAttribute('selected', 'selected');
    select.append(symbolsOption);
    h.OptionElement unitsOption = new h.OptionElement();
    unitsOption.value = 'units';
    unitsOption.appendText(LCDStrings.get('lm_units'));
    if (getAttribute('mode') == 'units')
      unitsOption.setAttribute('selected', 'selected');
    select.append(unitsOption);
    select.onInput.listen((h.Event event) {
      if (select.value == 'symbols' && getAttribute('mode') == 'units') {
        setAttribute('mode', 'symbols');
        input.setAttribute('data-unit_mode', 'false');
        js.context['LCMATH'].callMethod('initEditors');
      } else if (select.value == 'units' && getAttribute('mode') != 'units') {
        setAttribute('mode', 'units');
        input.setAttribute('data-unit_mode', 'true');
        js.context['LCMATH'].callMethod('initEditors');
      }
      input.focus();
    });
    editEl.append(select);
    el.replaceWith(editEl);
    input.focus();
    js.context['LCMATH'].callMethod('initEditors');
    var switchDisplay = () {
      String equationText = input.value;
      if (equationText != '') {
        if (firstChild != null)
          firstChild.nodeValue = equationText;
        else
          appendChild(new DNText(equationText));
      } else {
        if (firstChild != null)
          removeChild(firstChild);
      }
      editEl.replaceWith(html());
    };
    input.onBlur.listen((h.Event event) {
      Timer.run(() { // timer so that activeElement is updated
        if (h.document.activeElement == select)
          return;
        switchDisplay();
      });
    });
    select.onBlur.listen((h.Event event) {
      Timer.run(() {
        if (h.document.activeElement == input)
          return;
        switchDisplay();
      });
    });
    if (editEl is h.DivElement) {
      editEl.onClick.listen((h.MouseEvent event) {
        if (event.target != input && event.target != select) {
          page.moveCursorTo(new Position(parent, parent.offsetOf(this)+1));
        }
      });
    }
    input.onKeyDown.listen((h.KeyboardEvent event) {
      String equationText = input.value;
      int inputSize = input.size;
      if (equationText != null && equationText.length > inputSize)
        input.size = equationText.length;
    });
  }
}

Index: modules/damieng/graphical_editor/loncapa_daxe/web/nodes/option_foil.dart
+++ modules/damieng/graphical_editor/loncapa_daxe/web/nodes/option_foil.dart
/*
  This file is part of LONCAPA-Daxe.

  LONCAPA-Daxe 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 3 of the License, or
  (at your option) any later version.

  LONCAPA-Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of loncapa_daxe;

/**
 * Display for a foil inside an optionresponse.
 * Uses a simplified UI if the parent (OptionFoilgroup) is.
 */
class OptionFoil extends LCDBlock {
  
  OptionFoil.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
  }
  
  OptionFoil.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
    if ((parent as OptionFoilgroup).simpleUI)
      simpleUI = true;
  }
  
  /**
   * Returns true if the given node can be displayed with a simple UI.
   */
  bool simpleUIPossible() {
    List<String> possibleAttributes = ['value'];
    final RegExp numberedFoil = new RegExp("^[fF]oil[0-9]+\$");
    for (DaxeAttr att in attributes)
      if (!(att.name == 'value' ||
          (att.name == 'name' && numberedFoil.hasMatch(att.value)) ||
          (att.name == 'location' && att.value == 'random')))
        return false;
    return SimpleUIText.checkNodeContents(this);
  }
  
  @override
  h.Element html() {
    simpleUI = parent is OptionFoilgroup && (parent as OptionFoilgroup).simpleUI;
    if (!simpleUI)
      return super.html();
    h.TableRowElement tr = new h.TableRowElement();
    tr.id = id;
    tr.draggable = true;
    OptionFoilgroup group = parent;
    tr.onDragStart.listen((h.MouseEvent e) {
      e.dataTransfer.effectAllowed = 'move';
      e.dataTransfer.setData('text', toString());
      group.draggedFoil = this;
    });
    tr.onDragEnd.listen((h.MouseEvent e) {
      h.TableRowElement startTr = group.draggedFoil.getHTMLNode();
      startTr.classes.remove('dragafter');
      startTr.classes.remove('dragbefore');
      group.draggedFoil = null;
    });
    int enterLeaveCount = 0;
    tr.onDragEnter.listen((h.MouseEvent e) {
      if (group.draggedFoil == null)
        return;
      e.preventDefault();
      enterLeaveCount++;
      if (group.offsetOf(group.draggedFoil) < group.offsetOf(this))
        tr.classes.add('dragafter');
      else if (group.offsetOf(group.draggedFoil) > group.offsetOf(this))
        tr.classes.add('dragbefore');
    });
    tr.onDragLeave.listen((h.MouseEvent e) {
      if (group.draggedFoil == null)
        return;
      e.preventDefault();
      enterLeaveCount--;
      if (enterLeaveCount == 0) {
        tr.classes.remove('dragafter');
        tr.classes.remove('dragbefore');
      }
    });
    tr.onDragOver.listen((h.MouseEvent e) {
      e.preventDefault();
      e.dataTransfer.dropEffect = 'move';
    });
    tr.onDrop.listen((h.MouseEvent e) {
      e.preventDefault();
      enterLeaveCount--;
      if (enterLeaveCount == 0) {
        tr.classes.remove('dragafter');
        tr.classes.remove('dragbefore');
      }
      if (group.draggedFoil != null)
        drop(group.draggedFoil, this);
    });
    h.TableCellElement td = new h.TableCellElement();
    h.ImageElement grip = new h.ImageElement(src:'images/grip.png', width:8, height:16);
    grip.classes.add('grip-icon');
    grip.draggable = false;
    td.append(grip);
    h.SelectElement select = new h.SelectElement();
    String foilValue = getAttribute('value');
    List<String> options = (parent as OptionFoilgroup).options;
    for (String value in options) {
      h.OptionElement opte = new h.OptionElement(data:value, selected:value==foilValue);
      select.append(opte);
    }
    select.onChange.listen((h.Event event) {
      if (select.selectedIndex >= 0)
        doc.doNewEdit(new UndoableEdit.changeAttribute(this,
            new DaxeAttr('value', options[select.selectedIndex]), updateDisplay:false));
    });
    td.append(select);
    tr.append(td);
    td = new h.TableCellElement();
    for (DaxeNode dn=firstChild; dn!= null; dn=dn.nextSibling) {
      td.append(dn.html());
    }
    tr.append(td);
    td = new h.TableCellElement();
    h.ImageElement deleteImg = new h.ImageElement(src:'images/delete.png', width:16, height:16);
    deleteImg.classes.add('delete-icon');
    deleteImg.onClick.listen((h.MouseEvent e) => deleteFoil());
    td.append(deleteImg);
    tr.append(td);
    return tr;
  }
  
  @override
  h.Element getHTMLContentsNode() {
    if (!simpleUI)
      return super.getHTMLContentsNode();
    return(getHTMLNode().nodes[1]);
  }
  
  @override
  void updateAttributes() {
    updateHTML();
  }
  
  void drop(OptionFoil startFoil, OptionFoil targetFoil) {
    if (startFoil == null || targetFoil == null || startFoil == targetFoil)
      return;
    UndoableEdit edit = new UndoableEdit.compound(LCDStrings.get('foil_move'));
    edit.addSubEdit(new UndoableEdit.removeNode(startFoil));
    int offset = parent.offsetOf(targetFoil);
    edit.addSubEdit(new UndoableEdit.insertNode(new Position(parent, offset), startFoil));
    doc.doNewEdit(edit);
  }
  
  void deleteFoil() {
    doc.removeNode(this);
  }
  
  @override void afterInsert() {
    OptionFoilgroup group = parent as OptionFoilgroup;
    group.renameFoils(null);
    if (getAttribute('value') == null && group.options.length > 0)
      setAttribute('value', group.options[0]);
    group.updateHTML();
  }
  
  @override void beforeRemove() {
    (parent as OptionFoilgroup).renameFoils(this);
  }
}

Index: modules/damieng/graphical_editor/loncapa_daxe/web/nodes/option_foilgroup.dart
+++ modules/damieng/graphical_editor/loncapa_daxe/web/nodes/option_foilgroup.dart
/*
  This file is part of LONCAPA-Daxe.

  LONCAPA-Daxe 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 3 of the License, or
  (at your option) any later version.

  LONCAPA-Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of loncapa_daxe;

/**
 * This is used by OptionResponse for simple UI.
 */
class OptionFoilgroup extends LCDBlock {
  
  static List<String> simpleUIAttributes = ['options'];
  x.Element foilRef;
  List<String> options;
  OptionFoil draggedFoil;
  
  OptionFoilgroup.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
    initOptionFoilgroup(null);
  }
  
  OptionFoilgroup.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
    if ((parent as OptionResponse).simpleUI)
      simpleUI = true;
    initOptionFoilgroup(node);
  }
  
  /**
   * Returns true if the node can be displayed with a simple UI.
   */
  bool simpleUIPossible() {
    for (DaxeAttr att in attributes) {
      if (!simpleUIAttributes.contains(att.localName)) {
        return false;
      }
    }
    for (DaxeNode dn=firstChild; dn!= null; dn=dn.nextSibling) {
      if (dn is OptionFoil) {
        if (!dn.simpleUIPossible())
          return false;
      } else if (dn.nodeType != DaxeNode.TEXT_NODE || dn.nodeValue.trim() != '') {
        return false;
      }
    }
    return true;
  }
  
  void initOptionFoilgroup(x.Node domNode) {
    List<x.Element> foilRefs = doc.cfg.elementReferences('foil');
    foilRef = doc.cfg.findSubElement(ref, foilRefs);
    // remove unused foils
    DaxeNode next;
    for (DaxeNode dn=firstChild; dn!= null; dn=next) {
      next = dn.nextSibling;
      if (dn is OptionFoil && dn.getAttribute('value') == 'unused')
        removeChild(dn);
    }
    options = parseOptions();
    if (simpleUI && domNode != null && (domNode as x.Element).getElementsByTagName('foil').length == 0) {
      // new node from DOM in simpleUI: add a foil if there is none
      addFoil();
    }
    restrictedInserts = ['foil'];
    draggedFoil = null;
  }
  
  @override
  h.Element html() {
    simpleUI = parent is OptionResponse && (parent as OptionResponse).simpleUI;
    if (!simpleUI)
      return super.html();
    h.DivElement div = new h.DivElement();
    div.id = id;
    div.classes.add('option-foilgroup');
    div.append(optionsHtml());
    div.append(foilsHtml());
    return div;
  }
  
  @override
  void updateHTMLAfterChildrenChange(List<DaxeNode> changed) {
    updateHTML();
  }
  
  /**
   * Returns the HTML for editing the list of options.
   */
  h.Element optionsHtml() {
    h.DivElement div = new h.DivElement();
    div.id = "options$id";
    div.classes.add('optionresponse-options');
    div.appendText(LCDStrings.get('possible_options'));
    h.UListElement ul = new h.UListElement();
    ul.classes.add('option-list');
    int startIndex = -1;
    for (int i=0; i<options.length; i++) {
      String value = options[i];
      h.LIElement li = new h.LIElement();
      li.draggable = true;
      li.onDragStart.listen((h.MouseEvent e) {
        e.dataTransfer.effectAllowed = 'move';
        e.dataTransfer.setData('text', "option$i");
        startIndex = i; // see http://stackoverflow.com/q/11065803/438970
      });
      li.onDragEnd.listen((h.MouseEvent e) {
        startIndex = -1;
      });
      li.onDragOver.listen((h.MouseEvent e) {
        e.preventDefault();
        e.dataTransfer.dropEffect = 'move';
      });
      int enterLeaveCount = 0; // see http://stackoverflow.com/q/7110353/438970
      li.onDragEnter.listen((h.MouseEvent e) {
        if (startIndex == -1)
          return;
        e.preventDefault(); // for IE11 (without this there are more dragenter than dragleave)
        enterLeaveCount++;
        if (startIndex < i)
          li.classes.add('dragafter');
        else if (i < startIndex)
          li.classes.add('dragbefore');
      });
      li.onDragLeave.listen((h.MouseEvent e) {
        if (startIndex == -1)
          return;
        e.preventDefault();
        enterLeaveCount--;
        if (enterLeaveCount == 0) {
          li.classes.remove('dragafter');
          li.classes.remove('dragbefore');
        }
      });
      li.onDrop.listen((h.MouseEvent e) =>  drop(i, e));
      h.ImageElement grip = new h.ImageElement(src:'images/grip.png', width:8, height:16);
      grip.classes.add('grip-icon');
      grip.draggable = false;
      li.append(grip);
      h.TextInputElement textfield = new h.TextInputElement();
      textfield.value = value;
      textfield.onMouseEnter.listen((h.MouseEvent e) {
        // IE bug workaround
        // https://connect.microsoft.com/IE/feedback/details/927470/ie-11-input-field-of-type-text-does-not-respond-to-mouse-clicks-when-ancestor-node-has-draggable-true
        if (startIndex == -1)
          li.draggable = false;
      });
      textfield.onMouseLeave.listen((h.MouseEvent e) {
        if (!li.draggable)
          li.draggable = true;
      });
      textfield.onInput.listen((h.Event e) {
        UndoableEdit compound = new UndoableEdit.compound(LCDStrings.get('options_change'));
        for (DaxeNode dn=firstChild; dn!= null; dn=dn.nextSibling) {
          if (dn is OptionFoil && dn.getAttribute('value') == options[i])
            compound.addSubEdit(new UndoableEdit.changeAttribute(dn,
                new DaxeAttr('value', textfield.value), updateDisplay:false));
        }
        options[i] = textfield.value;
        compound.addSubEdit(saveOptionsEdit(updateDisplay:false));
        doc.doNewEdit(compound);
        updateFoilsHtml();
      });
      // NOTE: it is not possible to select text in the textfield
      // see http://stackoverflow.com/q/21680363/438970
      li.append(textfield);
      h.ImageElement deleteImg = new h.ImageElement(src:'images/delete.png', width:16, height:16);
      deleteImg.classes.add('delete-icon');
      deleteImg.onClick.listen((h.MouseEvent e) => deleteOption(i));
      li.append(deleteImg);
      ul.append(li);
    }
    div.append(ul);
    h.ButtonElement bAdd = new h.ButtonElement();
    bAdd.attributes['type'] = 'button';
    bAdd.text = LCDStrings.get('add_option');
    bAdd.onClick.listen((h.MouseEvent event) => addOption());
    div.append(bAdd);
    return div;
  }
  
  /**
   * Returns the HTML for editing the foils.
   */
  h.Element foilsHtml() {
    h.DivElement div = new h.DivElement();
    div.id = "foils$id";
    div.classes.add('optionresponse-foilgroup');
    div.appendText(LCDStrings.get('foils'));
    h.TableElement table = new h.TableElement();
    for (DaxeNode dn=firstChild; dn!= null; dn=dn.nextSibling) {
      if (dn is! OptionFoil)
        continue;
      table.append(dn.html());
    }
    div.append(table);
    h.ButtonElement bAdd = new h.ButtonElement();
    bAdd.attributes['type'] = 'button';
    bAdd.text = LCDStrings.get('add_foil');
    bAdd.onClick.listen((h.MouseEvent event) => addUndoableFoil());
    div.append(bAdd);
    return div;
  }
  
  void addOption() {
    options.add('');
    optionsChange();
  }
  
  void deleteOption(int index) {
    options.removeAt(index);
    optionsChange();
  }
  
  /**
   * Handle a change to the options
   */
  void optionsChange() {
    UndoableEdit compound = new UndoableEdit.compound(LCDStrings.get('options_change'));
    UndoableEdit optionEdit = saveOptionsEdit();
    compound.addSubEdit(optionEdit);
    if (options.length > 0) {
      for (DaxeNode dn in childNodes) {
        if (dn is OptionFoil) {
          String value = dn.getAttribute('value');
          if (value == null)
            compound.addSubEdit(new UndoableEdit.changeAttribute(dn, new DaxeAttr('value', options[0])));
          else if (options.indexOf(value) == -1) {
            // current foil value is no longer in the list: set to first option or remove
            String newValue;
            if (options.length > 0)
              newValue = options[0];
            else
              newValue = null;
            compound.addSubEdit(new UndoableEdit.changeAttribute(dn, new DaxeAttr('value', newValue)));
          }
        }
      }
    }
    doc.doNewEdit(compound);
  }
  
  void updateOptionsHtml() {
    h.document.getElementById("options$id").replaceWith(optionsHtml());
  }
  
  void updateFoilsHtml() {
    h.document.getElementById("foils$id").replaceWith(foilsHtml());
  }
  
  void drop(int targetIndex, h.MouseEvent e) {
    String data = e.dataTransfer.getData('text');
    e.preventDefault();
    if (!data.startsWith('option'))
      return;
    int startIndex = int.parse(data.substring(6));
    String value = options[startIndex];
    if (targetIndex > startIndex) {
      for (int i=startIndex; i<targetIndex; i++)
        options[i] = options[i+1];
      options[targetIndex] = value;
    } else if (targetIndex < startIndex) {
      for (int i=startIndex; i>targetIndex; i--)
        options[i] = options[i-1];
      options[targetIndex] = value;
    }
    optionsChange();
  }
  
  @override
  void newNodeCreationUI(ActionFunction okfct) {
    // called when a new element is inserted
    // automatically add a foil, do not display an attribute dialog
    if (simpleUI)
      addFoil();
    okfct();
  }
  
  @override
  void updateAttributes() {
    options = parseOptions();
    updateHTML();
  }
  
  /**
   * Add a foil (not using an UndoableEdit)
   */
  void addFoil() {
    OptionFoil foil = new OptionFoil.fromRef(foilRef);
    foil.setAttribute('name', 'foil1');
    if (options.length > 0)
      foil.setAttribute('value', options[0]);
    foil.state = 1;
    appendChild(foil);
  }
  
  /**
   * Add a foil (using an UndoableEdit)
   */
  void addUndoableFoil() {
    OptionFoil foil = new OptionFoil.fromRef(foilRef);
    if (options.length > 0)
      foil.setAttribute('value', options[0]);
    foil.state = 1;
    doc.insertNode(foil, new Position(this, offsetLength));
    page.moveCursorTo(new Position(foil, 0));
    page.updateAfterPathChange();
  }
  
  /**
   * Inserts a new foil after the given one (called when the user click on the button).
   */
  void insertFoil(OptionFoil foil) {
    OptionFoil newNode = new OptionFoil.fromRef(foilRef);
    if (options.length > 0)
      newNode.setAttribute('value', options[0]);
    foil.state = 1;
    Position pos;
    if (foil == null)
      pos = new Position(this, 0);
    else
      pos = new Position(this, offsetOf(foil)+1);
    doc.insertNode(newNode, pos);
    page.moveCursorTo(new Position(newNode, 0));
    page.updateAfterPathChange();
  }
  
  /**
   * Removes the given foil (called when the user click on the button).
   */
  void removeFoil(OptionFoil foil) {
    doc.doNewEdit(new UndoableEdit.removeNode(foil));
  }
  
  List<String> parseOptions() {
    String s = getAttribute('options');
    if (s == null)
      return new List<String>();
    s = s.replaceAllMapped(new RegExp(r'^\s*\((.*)\)\s*$'), (match) => match[1]);
    // FIXME: handle , and ' in values
    List<String> list = s.split(',');
    for (int i=0; i<list.length; i++)
      list[i] = list[i].replaceAllMapped(new RegExp(r"^\s*'(.*)'\s*$"), (match) => match[1]);
    return list;
  }
  
  UndoableEdit saveOptionsEdit({bool updateDisplay:true}) {
    StringBuffer sb = new StringBuffer();
    sb.write('(');
    for (int i=0; i<options.length; i++) {
      sb.write("'${options[i]}'");
      if (i < options.length - 1)
        sb.write(',');
    }
    sb.write(')');
    UndoableEdit edit = new UndoableEdit.changeAttribute(this,
        new DaxeAttr('options', sb.toString()), updateDisplay:updateDisplay);
    // FIXME: consecutive character additions should be merged into one Undo
    return edit;
  }
  
  void renameFoils(OptionFoil removedFoil) {
    // rename foils automatically only when all names are [fF]oil[0-9]+
    final RegExp numberedFoil = new RegExp("^[fF]oil[0-9]+\$");
    for (DaxeNode dn=firstChild; dn!= null; dn=dn.nextSibling) {
      String name = dn.getAttribute('name');
      if (name != null && !numberedFoil.hasMatch(name))
        return;
    }
    int nb = 1;
    for (DaxeNode dn=firstChild; dn!= null; dn=dn.nextSibling) {
      if (dn is! OptionFoil)
        continue;
      if (dn == removedFoil)
        continue;
      dn.setAttribute('name', "foil$nb");
      dn.updateValidity();
      nb++;
    }
  }
}

Index: modules/damieng/graphical_editor/loncapa_daxe/web/nodes/option_response.dart
+++ modules/damieng/graphical_editor/loncapa_daxe/web/nodes/option_response.dart
/*
  This file is part of LONCAPA-Daxe.

  LONCAPA-Daxe 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 3 of the License, or
  (at your option) any later version.

  LONCAPA-Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of loncapa_daxe;

/**
 * Display for optionresponse, with a possible simple UI.
 * Jaxe display type: 'optionresponse'.
 */
class OptionResponse extends LCDBlock {
  
  static List<String> simpleUIAttributes = ['max', 'randomize'];
  bool displaySimpleButton = true;
  
  OptionResponse.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
    initOptionResponse(null);
  }
  
  OptionResponse.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
    initOptionResponse(node);
  }
  
  /**
   * Returns true if the node can be displayed with a simple UI.
   */
  bool simpleUIPossible() {
    for (DaxeAttr att in attributes) {
      if (!simpleUIAttributes.contains(att.localName)) {
        return false;
      }
    }
    for (DaxeNode dn=firstChild; dn!= null; dn=dn.nextSibling) {
      if (dn is OptionFoilgroup) {
        if (!(dn as OptionFoilgroup).simpleUIPossible())
          return false;
      } else if (dn is Hintgroup) {
        if (!(dn as Hintgroup).simpleUIPossible())
          return false;
      } else if (dn.nodeType != DaxeNode.TEXT_NODE || dn.nodeValue.trim() != '') {
        return false;
      }
    }
    return true;
  }
  
  void initOptionResponse(x.Node domNode) {
    simpleUI = simpleUIPossible();
    if (simpleUI && domNode != null) {
      // new node from DOM: add a foilgroup and hintgroup if missing
      if ((domNode as x.Element).getElementsByTagName('foilgroup').length == 0)
        appendChild(newFoilgroup());
      Hintgroup hintgroup = null;
      for (DaxeNode dn in childNodes) {
        if (dn is Hintgroup) {
          hintgroup = dn;
          break;
        }
      }
      if (hintgroup != null)
        hintgroup.fixStructureForSimpleUI();
      else
        appendChild(newHintgroup());
    }
    if (simpleUI)
      setupRestrictions();
  }
  
  void setupRestrictions() {
    for (int i=0; i<attRefs.length; i++) {
      x.Element refAttr = attRefs[i];
      if (!simpleUIAttributes.contains(doc.cfg.attributeName(refAttr))) {
        attRefs.removeAt(i);
        i--;
      }
    }
    restrictedInserts = [];
  }
  
  @override
  void setupSimpleUI() {
    setupRestrictions();
    autoInsertFoilgroupAndHintgroup();
  }
  
  @override
  void newNodeCreationUI(ActionFunction okfct) {
    // called when a new element is inserted
    // automatically add (without undo) a foilgroup and hintgroup, do not display an attribute dialog
    if (simpleUI) {
      appendChild(newFoilgroup());
      appendChild(newHintgroup());
    }
    okfct();
  }
  
  OptionFoilgroup newFoilgroup() {
    List<x.Element> foilgroupRefs = doc.cfg.elementReferences('foilgroup');
    x.Element foilgroupRef = doc.cfg.findSubElement(ref, foilgroupRefs);
    OptionFoilgroup foilgroup = new OptionFoilgroup.fromRef(foilgroupRef);
    foilgroup.setAttribute('options',"('')");
    foilgroup.options = foilgroup.parseOptions();
    foilgroup.addFoil();
    return foilgroup;
  }
  
  Hintgroup newHintgroup() {
    List<x.Element> hintgroupRefs = doc.cfg.elementReferences('hintgroup');
    x.Element hintgroupRef = doc.cfg.findSubElement(ref, hintgroupRefs);
    Hintgroup hintgroup = new Hintgroup.fromRef(hintgroupRef);
    hintgroup.state = 1;
    return hintgroup;
  }
  
  /**
   * This inserts a foilgroup and hintgroup if missing, as if the user had done it
   * (with undoable edits).
   */
  void autoInsertFoilgroupAndHintgroup() {
    bool foundFoilgroup = false;
    bool foundHintgroup = false;
    for (DaxeNode dn in childNodes) {
      if (dn.nodeName == 'foilgroup')
        foundFoilgroup = true;
      if (dn.nodeName == 'hintgroup')
        foundHintgroup = true;
    }
    if (!foundFoilgroup)
      doc.insertNode(newFoilgroup(), new Position(this, 0));
    if (!foundHintgroup)
      doc.insertNode(newHintgroup(), new Position(this, offsetLength));
  }
}

Index: modules/damieng/graphical_editor/loncapa_daxe/web/nodes/radio_foil.dart
+++ modules/damieng/graphical_editor/loncapa_daxe/web/nodes/radio_foil.dart
/*
  This file is part of LONCAPA-Daxe.

  LONCAPA-Daxe 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 3 of the License, or
  (at your option) any later version.

  LONCAPA-Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of loncapa_daxe;

/**
 * Display for a foil inside a radiobuttonresponse.
 * Uses a simplified UI if the parent (RadioFoilgroup) is.
 */
class RadioFoil extends LCDBlock {
  
  RadioFoil.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
  }
  
  RadioFoil.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
    if ((parent as RadioFoilgroup).simpleUI)
      simpleUI = true;
  }
  
  /**
   * Returns true if the given node can be displayed with a simple UI.
   */
  bool simpleUIPossible() {
    List<String> possibleAttributes = ['value'];
    final RegExp numberedFoil = new RegExp("^(radio)?[fF]oil[0-9]+\$");
    for (DaxeAttr att in attributes)
      if (!(att.name == 'value' ||
          (att.name == 'name' && numberedFoil.hasMatch(att.value)) ||
          (att.name == 'location' && att.value == 'random')))
        return false;
    return SimpleUIText.checkNodeContents(this);
  }
  
  @override
  h.Element html() {
    simpleUI = parent is RadioFoilgroup && (parent as RadioFoilgroup).simpleUI;
    if (!simpleUI)
      return super.html();
    h.TableRowElement tr = new h.TableRowElement();
    tr.id = id;
    tr.draggable = true;
    RadioFoilgroup group = parent;
    tr.onDragStart.listen((h.MouseEvent e) {
      e.dataTransfer.effectAllowed = 'move';
      e.dataTransfer.setData('text', toString());
      group.draggedFoil = this;
    });
    tr.onDragEnd.listen((h.MouseEvent e) {
      h.TableRowElement startTr = group.draggedFoil.getHTMLNode();
      startTr.classes.remove('dragafter');
      startTr.classes.remove('dragbefore');
      group.draggedFoil = null;
    });
    int enterLeaveCount = 0;
    tr.onDragEnter.listen((h.MouseEvent e) {
      if (group.draggedFoil == null)
        return;
      e.preventDefault();
      enterLeaveCount++;
      if (group.offsetOf(group.draggedFoil) < group.offsetOf(this))
        tr.classes.add('dragafter');
      else if (group.offsetOf(group.draggedFoil) > group.offsetOf(this))
        tr.classes.add('dragbefore');
    });
    tr.onDragLeave.listen((h.MouseEvent e) {
      if (group.draggedFoil == null)
        return;
      e.preventDefault();
      enterLeaveCount--;
      if (enterLeaveCount == 0) {
        tr.classes.remove('dragafter');
        tr.classes.remove('dragbefore');
      }
    });
    tr.onDragOver.listen((h.MouseEvent e) {
      e.preventDefault();
      e.dataTransfer.dropEffect = 'move';
    });
    tr.onDrop.listen((h.MouseEvent e) {
      e.preventDefault();
      enterLeaveCount--;
      if (enterLeaveCount == 0) {
        tr.classes.remove('dragafter');
        tr.classes.remove('dragbefore');
      }
      if (group.draggedFoil != null)
        drop(group.draggedFoil, this);
    });
    h.TableCellElement td = new h.TableCellElement();
    h.ImageElement grip = new h.ImageElement(src:'images/grip.png', width:8, height:16);
    grip.classes.add('grip-icon');
    grip.draggable = false;
    td.append(grip);
    h.RadioButtonInputElement radio = new h.RadioButtonInputElement();
    radio.name = parent.id;
    radio.title = LCDStrings.get('click_to_select');
    if (getAttribute('value') == 'true')
      radio.checked = true;
    radio.onChange.listen((h.Event event) {
      if (radio.checked)
        (parent as RadioFoilgroup).selectFoil(this);
    });
    td.append(radio);
    tr.append(td);
    td = new h.TableCellElement();
    for (DaxeNode dn=firstChild; dn!= null; dn=dn.nextSibling) {
      td.append(dn.html());
    }
    tr.append(td);
    td = new h.TableCellElement();
    h.ImageElement deleteImg = new h.ImageElement(src:'images/delete.png', width:16, height:16);
    deleteImg.classes.add('delete-icon');
    deleteImg.onClick.listen((h.MouseEvent e) => (parent as RadioFoilgroup).removeFoil(this));
    td.append(deleteImg);
    tr.append(td);
    return tr;
  }
  
  void drop(RadioFoil startFoil, RadioFoil targetFoil) {
    if (startFoil == null || targetFoil == null || startFoil == targetFoil)
      return;
    UndoableEdit edit = new UndoableEdit.compound(LCDStrings.get('foil_move'));
    edit.addSubEdit(new UndoableEdit.removeNode(startFoil));
    int offset = parent.offsetOf(targetFoil);
    edit.addSubEdit(new UndoableEdit.insertNode(new Position(parent, offset), startFoil));
    doc.doNewEdit(edit);
  }
  
  @override
  h.Element getHTMLContentsNode() {
    if (!simpleUI)
      return super.getHTMLContentsNode();
    return(getHTMLNode().nodes[1]);
  }
  
  @override
  void updateAttributes() {
    updateHTML();
  }
  
  @override void afterInsert() {
    (parent as RadioFoilgroup).renameFoils(null);
    if (getAttribute('value') == null)
      setAttribute('value', 'false');
    (parent as RadioFoilgroup).updateHTML();
  }
  
  @override void beforeRemove() {
    (parent as RadioFoilgroup).renameFoils(this);
  }
}

Index: modules/damieng/graphical_editor/loncapa_daxe/web/nodes/radio_foilgroup.dart
+++ modules/damieng/graphical_editor/loncapa_daxe/web/nodes/radio_foilgroup.dart
/*
  This file is part of LONCAPA-Daxe.

  LONCAPA-Daxe 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 3 of the License, or
  (at your option) any later version.

  LONCAPA-Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of loncapa_daxe;

/**
 * This is used by RadioResponse for simple UI.
 */
class RadioFoilgroup extends LCDBlock {
  
  x.Element foilRef;
  RadioFoil draggedFoil;
  
  RadioFoilgroup.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
    initRadioFoilgroup(null);
  }
  
  RadioFoilgroup.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
    if ((parent as RadioResponse).simpleUI)
      simpleUI = true;
    initRadioFoilgroup(node);
  }
  
  /**
   * Returns true if the node can be displayed with a simple UI.
   */
  bool simpleUIPossible() {
    if (attributes.length != 0)
      return false;
    bool trueFoil = false;
    for (DaxeNode dn=firstChild; dn!= null; dn=dn.nextSibling) {
      if (dn is RadioFoil) {
        if (!dn.simpleUIPossible())
          return false;
        if (dn.getAttribute('value') == 'true') {
          if (trueFoil)
            return false; // this is radioresponse, not checkboxresponse !
          trueFoil = true;
        }
      } else if (dn.nodeType != DaxeNode.TEXT_NODE || dn.nodeValue.trim() != '') {
        return false;
      }
    }
    return true;
  }
  
  void initRadioFoilgroup(x.Node domNode) {
    List<x.Element> foilRefs = doc.cfg.elementReferences('foil');
    foilRef = doc.cfg.findSubElement(ref, foilRefs);
    // remove unused foils
    DaxeNode next;
    for (DaxeNode dn=firstChild; dn!= null; dn=next) {
      next = dn.nextSibling;
      if (dn is RadioFoil && dn.getAttribute('value') == 'unused')
        removeChild(dn);
    }
    if (simpleUI && domNode != null && (domNode as x.Element).getElementsByTagName('foil').length == 0) {
      // new node from DOM in simpleUI: add a foil if there is none
      addFirstFoil();
    }
    restrictedInserts = ['foil'];
  }
  
  @override
  h.Element html() {
    simpleUI = parent is RadioResponse && (parent as RadioResponse).simpleUI;
    if (!simpleUI)
      return super.html();
    h.DivElement div = new h.DivElement();
    div.id = id;
    div.classes.add('radio-foilgroup');
    h.TableElement table = new h.TableElement();
    for (DaxeNode dn=firstChild; dn!= null; dn=dn.nextSibling) {
      if (dn is! RadioFoil)
        continue;
      table.append(dn.html());
    }
    div.append(table);
    h.ButtonElement bAdd = new h.ButtonElement();
    bAdd.attributes['type'] = 'button';
    bAdd.text = LCDStrings.get('add_foil');
    bAdd.onClick.listen((h.MouseEvent event) => addUndoableFoil());
    div.append(bAdd);
    return div;
  }
  
  @override
  void newNodeCreationUI(ActionFunction okfct) {
    // called when a new element is inserted
    // automatically add a foil, do not display an attribute dialog
    if (simpleUI)
      addFirstFoil();
    okfct();
  }
  
  @override
  void updateHTMLAfterChildrenChange(List<DaxeNode> changed) {
    updateHTML(); // because of renamed foils
  }
  
  /**
   * Adds a foil (not using an UndoableEdit)
   */
  void addFirstFoil() {
    RadioFoil foil = new RadioFoil.fromRef(foilRef);
    foil.setAttribute('name', 'radiofoil1');
    foil.setAttribute('value', 'true');
    foil.state = 1;
    appendChild(foil);
  }
  
  /**
   * Adds a foil at the end (using an UndoableEdit)
   */
  void addUndoableFoil() {
    RadioFoil foil = new RadioFoil.fromRef(foilRef);
    foil.state = 1;
    Position pos = new Position(this, offsetLength);
    doc.insertNode(foil, pos);
    page.moveCursorTo(new Position(foil, 0));
    page.updateAfterPathChange();
  }
  
  /**
   * Removes the given foil (called when the user click on the button).
   */
  void removeFoil(RadioFoil foil) {
    doc.doNewEdit(new UndoableEdit.removeNode(foil));
  }
  
  /**
   * Selects the given foil.
   */
  void selectFoil(RadioFoil foil) {
    UndoableEdit edit = new UndoableEdit.compound(LCDStrings.get('foil_selection'));
    for (DaxeNode dn=firstChild; dn!= null; dn=dn.nextSibling) {
      if (dn is! RadioFoil)
        continue;
      DaxeAttr attr = new DaxeAttr('value', dn == foil ? 'true' : 'false');
      edit.addSubEdit(new UndoableEdit.changeAttribute(dn, attr));
    }
    doc.doNewEdit(edit);
  }
  
  void renameFoils(RadioFoil removedFoil) {
    // rename foils automatically only when all names are (radio)?[fF]oil[0-9]+
    final RegExp numberedFoil = new RegExp("^(radio)?[fF]oil[0-9]+\$");
    for (DaxeNode dn=firstChild; dn!= null; dn=dn.nextSibling) {
      String name = dn.getAttribute('name');
      if (name != null && !numberedFoil.hasMatch(name))
        return;
    }
    int nb = 1;
    for (DaxeNode dn=firstChild; dn!= null; dn=dn.nextSibling) {
      if (dn is! RadioFoil)
        continue;
      if (dn == removedFoil)
        continue;
      dn.setAttribute('name', "radiofoil$nb");
      dn.updateValidity();
      nb++;
    }
  }
  
}

Index: modules/damieng/graphical_editor/loncapa_daxe/web/nodes/radio_response.dart
+++ modules/damieng/graphical_editor/loncapa_daxe/web/nodes/radio_response.dart
/*
  This file is part of LONCAPA-Daxe.

  LONCAPA-Daxe 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 3 of the License, or
  (at your option) any later version.

  LONCAPA-Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of loncapa_daxe;

/**
 * Display for radiobuttonresponse, with a possible simple UI.
 * Jaxe display type: 'radioresponse'.
 */
class RadioResponse extends LCDBlock {
  
  static List<String> simpleUIAttributes = ['max', 'randomize'];
  bool displaySimpleButton = true;
  
  RadioResponse.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
    initRadioResponse(null);
  }
  
  RadioResponse.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
    initRadioResponse(node);
  }
  
  /**
   * Returns true if the node can be displayed with a simple UI.
   */
  bool simpleUIPossible() {
    for (DaxeAttr att in attributes) {
      if (!simpleUIAttributes.contains(att.localName) && att.localName != 'id') {
        return false;
      }
    }
    for (DaxeNode dn=firstChild; dn!= null; dn=dn.nextSibling) {
      if (dn is RadioFoilgroup) {
        if (!dn.simpleUIPossible())
          return false;
      } else if (dn is Hintgroup) {
        if (!dn.simpleUIPossible())
          return false;
      } else if (dn.nodeType != DaxeNode.TEXT_NODE || dn.nodeValue.trim() != '') {
        return false;
      }
    }
    return true;
  }
  
  void initRadioResponse(x.Node domNode) {
    simpleUI = simpleUIPossible();
    if (simpleUI && domNode != null) {
      // new node from DOM: add a foilgroup and hintgroup if missing
      if ((domNode as x.Element).getElementsByTagName('foilgroup').length == 0)
        appendChild(newFoilgroup());
      Hintgroup hintgroup = null;
      for (DaxeNode dn in childNodes) {
        if (dn is Hintgroup) {
          hintgroup = dn;
          break;
        }
      }
      if (hintgroup != null)
        hintgroup.fixStructureForSimpleUI();
      else
        appendChild(newHintgroup());
    }
    if (simpleUI)
      setupRestrictions();
  }
  
  void setupRestrictions() {
    for (int i=0; i<attRefs.length; i++) {
      x.Element refAttr = attRefs[i];
      if (!simpleUIAttributes.contains(doc.cfg.attributeName(refAttr))) {
        attRefs.removeAt(i);
        i--;
      }
    }
    restrictedInserts = [];
  }
  
  @override
  void setupSimpleUI() {
    setupRestrictions();
    autoInsertFoilgroupAndHintgroup();
  }
  
  @override
  void newNodeCreationUI(ActionFunction okfct) {
    // called when a new element is inserted
    // automatically add (without undo) a foilgroup and hintgroup, do not display an attribute dialog
    if (simpleUI) {
      appendChild(newFoilgroup());
      appendChild(newHintgroup());
    }
    okfct();
  }
  
  RadioFoilgroup newFoilgroup() {
    List<x.Element> foilgroupRefs = doc.cfg.elementReferences('foilgroup');
    x.Element foilgroupRef = doc.cfg.findSubElement(ref, foilgroupRefs);
    RadioFoilgroup foilgroup = new RadioFoilgroup.fromRef(foilgroupRef);
    foilgroup.addFirstFoil();
    return foilgroup;
  }
  
  Hintgroup newHintgroup() {
    List<x.Element> hintgroupRefs = doc.cfg.elementReferences('hintgroup');
    x.Element hintgroupRef = doc.cfg.findSubElement(ref, hintgroupRefs);
    Hintgroup hintgroup = new Hintgroup.fromRef(hintgroupRef);
    hintgroup.state = 1;
    return hintgroup;
  }
  
  /**
   * This inserts a foilgroup and hintgroup if missing, as if the user had done it
   * (with undoable edits).
   */
  void autoInsertFoilgroupAndHintgroup() {
    bool foundFoilgroup = false;
    bool foundHintgroup = false;
    for (DaxeNode dn in childNodes) {
      if (dn.nodeName == 'foilgroup')
        foundFoilgroup = true;
      if (dn.nodeName == 'hintgroup')
        foundHintgroup = true;
    }
    if (!foundFoilgroup)
      doc.insertNode(newFoilgroup(), new Position(this, 0));
    if (!foundHintgroup)
      doc.insertNode(newHintgroup(), new Position(this, offsetLength));
  }
}

Index: modules/damieng/graphical_editor/loncapa_daxe/web/nodes/rank_foil.dart
+++ modules/damieng/graphical_editor/loncapa_daxe/web/nodes/rank_foil.dart
/*
  This file is part of LONCAPA-Daxe.

  LONCAPA-Daxe 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 3 of the License, or
  (at your option) any later version.

  LONCAPA-Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of loncapa_daxe;

/**
 * Display for a foil inside a rankresponse.
 * Uses a simplified UI if the parent (RankFoilgroup) is.
 */
class RankFoil extends LCDBlock {
  
  RankFoil.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
  }
  
  RankFoil.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
    if ((parent as RankFoilgroup).simpleUI)
      simpleUI = true;
  }
  
  /**
   * Returns true if the given node can be displayed with a simple UI.
   */
  bool simpleUIPossible() {
    List<String> possibleAttributes = ['value'];
    final RegExp numberedFoil = new RegExp("^[fF]oil[0-9]+\$");
    final RegExp positiveInteger = new RegExp("^[0-9]+\$");
    for (DaxeAttr att in attributes)
      if (!(att.name == 'value' && positiveInteger.hasMatch(att.value) ||
          (att.name == 'name' && numberedFoil.hasMatch(att.value)) ||
          (att.name == 'location' && att.value == 'random')))
        return false;
    return SimpleUIText.checkNodeContents(this);
  }
  
  @override
  h.Element html() {
    simpleUI = parent is RankFoilgroup && (parent as RankFoilgroup).simpleUI;
    if (!simpleUI)
      return super.html();
    h.TableRowElement tr = new h.TableRowElement();
    tr.id = id;
    tr.draggable = true;
    RankFoilgroup group = parent;
    tr.onDragStart.listen((h.MouseEvent e) {
      e.dataTransfer.effectAllowed = 'move';
      e.dataTransfer.setData('text', toString());
      group.draggedFoil = this;
    });
    tr.onDragEnd.listen((h.MouseEvent e) {
      h.TableRowElement startTr = group.draggedFoil.getHTMLNode();
      startTr.classes.remove('dragafter');
      startTr.classes.remove('dragbefore');
      group.draggedFoil = null;
    });
    int enterLeaveCount = 0;
    tr.onDragEnter.listen((h.MouseEvent e) {
      if (group.draggedFoil == null)
        return;
      e.preventDefault();
      enterLeaveCount++;
      if (group.offsetOf(group.draggedFoil) < group.offsetOf(this))
        tr.classes.add('dragafter');
      else if (group.offsetOf(group.draggedFoil) > group.offsetOf(this))
        tr.classes.add('dragbefore');
    });
    tr.onDragLeave.listen((h.MouseEvent e) {
      if (group.draggedFoil == null)
        return;
      e.preventDefault();
      enterLeaveCount--;
      if (enterLeaveCount == 0) {
        tr.classes.remove('dragafter');
        tr.classes.remove('dragbefore');
      }
    });
    tr.onDragOver.listen((h.MouseEvent e) {
      e.preventDefault();
      e.dataTransfer.dropEffect = 'move';
    });
    tr.onDrop.listen((h.MouseEvent e) {
      e.preventDefault();
      enterLeaveCount--;
      if (enterLeaveCount == 0) {
        tr.classes.remove('dragafter');
        tr.classes.remove('dragbefore');
      }
      if (group.draggedFoil != null)
        drop(group.draggedFoil, this);
    });
    h.TableCellElement td = new h.TableCellElement();
    h.ImageElement grip = new h.ImageElement(src:'images/grip.png', width:8, height:16);
    grip.classes.add('grip-icon');
    grip.draggable = false;
    td.append(grip);
    h.SpanElement rankNumberSpan = new h.SpanElement();
    rankNumberSpan.classes.add('rank-number');
    int nb = 1;
    for (DaxeNode dn=previousSibling; dn!=null; dn=dn.previousSibling) {
      if (dn is RankFoil)
        nb++;
    }
    rankNumberSpan.appendText("$nb");
    td.append(rankNumberSpan);
    tr.append(td);
    td = new h.TableCellElement();
    for (DaxeNode dn=firstChild; dn!= null; dn=dn.nextSibling) {
      td.append(dn.html());
    }
    tr.append(td);
    td = new h.TableCellElement();
    h.ImageElement deleteImg = new h.ImageElement(src:'images/delete.png', width:16, height:16);
    deleteImg.classes.add('delete-icon');
    deleteImg.onClick.listen((h.MouseEvent e) => (parent as RankFoilgroup).removeFoil(this));
    td.append(deleteImg);
    tr.append(td);
    return tr;
  }
  
  void drop(RankFoil startFoil, RankFoil targetFoil) {
    if (startFoil == null || targetFoil == null || startFoil == targetFoil)
      return;
    UndoableEdit edit = new UndoableEdit.compound(LCDStrings.get('foil_move'));
    edit.addSubEdit(new UndoableEdit.removeNode(startFoil));
    int offset = parent.offsetOf(targetFoil);
    edit.addSubEdit(new UndoableEdit.insertNode(new Position(parent, offset), startFoil));
    doc.doNewEdit(edit);
  }
  
  @override
  h.Element getHTMLContentsNode() {
    if (!simpleUI)
      return super.getHTMLContentsNode();
    return(getHTMLNode().nodes[1]);
  }
  
  @override
  void updateAttributes() {
    updateHTML();
  }
  
  @override void afterInsert() {
    (parent as RankFoilgroup).renameFoils(null);
    (parent as RankFoilgroup).updateHTML();
  }
  
  @override void beforeRemove() {
    (parent as RankFoilgroup).renameFoils(this);
  }
}

Index: modules/damieng/graphical_editor/loncapa_daxe/web/nodes/rank_foilgroup.dart
+++ modules/damieng/graphical_editor/loncapa_daxe/web/nodes/rank_foilgroup.dart
/*
  This file is part of LONCAPA-Daxe.

  LONCAPA-Daxe 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 3 of the License, or
  (at your option) any later version.

  LONCAPA-Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of loncapa_daxe;

/**
 * This is used by RankResponse for simple UI.
 */
class RankFoilgroup extends LCDBlock {
  
  x.Element foilRef;
  RankFoil draggedFoil;
  
  RankFoilgroup.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
    initRankFoilgroup(null);
  }
  
  RankFoilgroup.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
    if ((parent as RankResponse).simpleUI)
      simpleUI = true;
    initRankFoilgroup(node);
  }
  
  /**
   * Returns true if the node can be displayed with a simple UI.
   */
  bool simpleUIPossible() {
    if (attributes.length != 0)
      return false;
    for (DaxeNode dn=firstChild; dn!= null; dn=dn.nextSibling) {
      if (dn is RankFoil) {
        if (!dn.simpleUIPossible())
          return false;
      } else if (dn.nodeType != DaxeNode.TEXT_NODE || dn.nodeValue.trim() != '') {
        return false;
      }
    }
    return true;
  }
  
  void initRankFoilgroup(x.Node domNode) {
    List<x.Element> foilRefs = doc.cfg.elementReferences('foil');
    foilRef = doc.cfg.findSubElement(ref, foilRefs);
    if (simpleUI && domNode != null && (domNode as x.Element).getElementsByTagName('foil').length == 0) {
      // new node from DOM in simpleUI: add a foil if there is none
      addFirstFoil();
    }
    restrictedInserts = ['foil'];
  }
  
  @override
  h.Element html() {
    simpleUI = parent is RankResponse && (parent as RankResponse).simpleUI;
    if (!simpleUI)
      return super.html();
    h.DivElement div = new h.DivElement();
    div.id = id;
    div.classes.add('rank-foilgroup');
    h.TableElement table = new h.TableElement();
    for (DaxeNode dn=firstChild; dn!= null; dn=dn.nextSibling) {
      if (dn is! RankFoil)
        continue;
      table.append(dn.html());
    }
    div.append(table);
    h.ButtonElement bAdd = new h.ButtonElement();
    bAdd.attributes['type'] = 'button';
    bAdd.text = LCDStrings.get('add_foil');
    bAdd.onClick.listen((h.MouseEvent event) => addUndoableFoil());
    div.append(bAdd);
    return div;
  }
  
  @override
  void newNodeCreationUI(ActionFunction okfct) {
    // called when a new element is inserted
    // automatically add a foil, do not display an attribute dialog
    if (simpleUI)
      addFirstFoil();
    okfct();
  }
  
  @override
  void updateHTMLAfterChildrenChange(List<DaxeNode> changed) {
    updateHTML(); // because of renamed foils
  }
  
  /**
   * Adds a foil (not using an UndoableEdit)
   */
  void addFirstFoil() {
    RankFoil foil = new RankFoil.fromRef(foilRef);
    foil.setAttribute('name', 'foil1');
    foil.setAttribute('value', '1');
    foil.state = 1;
    appendChild(foil);
  }
  
  /**
   * Adds a foil at the end (using an UndoableEdit)
   */
  void addUndoableFoil() {
    RankFoil foil = new RankFoil.fromRef(foilRef);
    foil.state = 1;
    Position pos = new Position(this, offsetLength);
    doc.insertNode(foil, pos);
    page.moveCursorTo(new Position(foil, 0));
    page.updateAfterPathChange();
  }
  
  /**
   * Removes the given foil (called when the user click on the button).
   */
  void removeFoil(RankFoil foil) {
    doc.doNewEdit(new UndoableEdit.removeNode(foil));
  }
  
  void renameFoils(RankFoil removedFoil) {
    // rename foils automatically only when all names are [fF]oil[0-9]+
    // and values are [0-9]+
    final RegExp numberedFoil = new RegExp("^[fF]oil[0-9]+\$");
    final RegExp positiveInteger = new RegExp("^[0-9]+\$");
    for (DaxeNode dn=firstChild; dn!= null; dn=dn.nextSibling) {
      String name = dn.getAttribute('name');
      if (name != null && !numberedFoil.hasMatch(name))
        return;
      String value = dn.getAttribute('value');
      if (value != null && !positiveInteger.hasMatch(value))
        return;
    }
    int nb = 1;
    for (DaxeNode dn=firstChild; dn!= null; dn=dn.nextSibling) {
      if (dn is! RankFoil)
        continue;
      if (dn == removedFoil)
        continue;
      dn.setAttribute('name', "foil$nb");
      dn.setAttribute('value', "$nb");
      dn.updateValidity();
      nb++;
    }
  }
  
}

Index: modules/damieng/graphical_editor/loncapa_daxe/web/nodes/rank_response.dart
+++ modules/damieng/graphical_editor/loncapa_daxe/web/nodes/rank_response.dart
/*
  This file is part of LONCAPA-Daxe.

  LONCAPA-Daxe 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 3 of the License, or
  (at your option) any later version.

  LONCAPA-Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of loncapa_daxe;

/**
 * Display for rankresponse, with a possible simple UI.
 * Jaxe display type: 'rankresponse'.
 */
class RankResponse extends LCDBlock {
  
  static List<String> simpleUIAttributes = ['max', 'randomize'];
  bool displaySimpleButton = true;
  
  RankResponse.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
    initRankResponse(null);
  }
  
  RankResponse.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
    initRankResponse(node);
  }
  
  /**
   * Returns true if the node can be displayed with a simple UI.
   */
  bool simpleUIPossible() {
    for (DaxeAttr att in attributes) {
      if (!simpleUIAttributes.contains(att.localName))
        return false;
      if (att.name == 'randomize' && att.value != null && att.value != 'yes')
        return false;
    }
    for (DaxeNode dn=firstChild; dn!= null; dn=dn.nextSibling) {
      if (dn is RankFoilgroup) {
        if (!dn.simpleUIPossible())
          return false;
      } else if (dn is Hintgroup) {
        if (!dn.simpleUIPossible())
          return false;
      } else if (dn.nodeType != DaxeNode.TEXT_NODE || dn.nodeValue.trim() != '') {
        return false;
      }
    }
    return true;
  }
  
  void initRankResponse(x.Node domNode) {
    simpleUI = simpleUIPossible();
    if (simpleUI && domNode != null) {
      // new node from DOM: add a foilgroup and hintgroup if missing
      if ((domNode as x.Element).getElementsByTagName('foilgroup').length == 0)
        appendChild(newFoilgroup());
      if ((domNode as x.Element).getElementsByTagName('hintgroup').length == 0)
        appendChild(newHintgroup());
    }
    if (simpleUI)
      setupRestrictions();
  }
  
  void setupRestrictions() {
    for (int i=0; i<attRefs.length; i++) {
      x.Element refAttr = attRefs[i];
      if (!simpleUIAttributes.contains(doc.cfg.attributeName(refAttr))) {
        attRefs.removeAt(i);
        i--;
      }
    }
    restrictedInserts = [];
  }
  
  @override
  void setupSimpleUI() {
    setupRestrictions();
    autoInsertFoilgroupAndHintgroup();
  }
  
  @override
  void newNodeCreationUI(ActionFunction okfct) {
    // called when a new element is inserted
    // automatically add (without undo) a foilgroup and hintgroup, do not display an attribute dialog
    if (simpleUI) {
      appendChild(newFoilgroup());
      appendChild(newHintgroup());
    }
    okfct();
  }
  
  RankFoilgroup newFoilgroup() {
    List<x.Element> foilgroupRefs = doc.cfg.elementReferences('foilgroup');
    x.Element foilgroupRef = doc.cfg.findSubElement(ref, foilgroupRefs);
    RankFoilgroup foilgroup = new RankFoilgroup.fromRef(foilgroupRef);
    foilgroup.addFirstFoil();
    return foilgroup;
  }
  
  Hintgroup newHintgroup() {
    List<x.Element> hintgroupRefs = doc.cfg.elementReferences('hintgroup');
    x.Element hintgroupRef = doc.cfg.findSubElement(ref, hintgroupRefs);
    Hintgroup hintgroup = new Hintgroup.fromRef(hintgroupRef);
    hintgroup.state = 1;
    return hintgroup;
  }
  
  /**
   * This inserts a foilgroup and hintgroup if missing, as if the user had done it
   * (with undoable edits).
   */
  void autoInsertFoilgroupAndHintgroup() {
    bool foundFoilgroup = false;
    bool foundHintgroup = false;
    for (DaxeNode dn in childNodes) {
      if (dn.nodeName == 'foilgroup')
        foundFoilgroup = true;
      if (dn.nodeName == 'hintgroup')
        foundHintgroup = true;
    }
    if (!foundFoilgroup)
      doc.insertNode(newFoilgroup(), new Position(this, 0));
    if (!foundHintgroup)
      doc.insertNode(newHintgroup(), new Position(this, offsetLength));
  }
}

Index: modules/damieng/graphical_editor/loncapa_daxe/web/nodes/script_block.dart
+++ modules/damieng/graphical_editor/loncapa_daxe/web/nodes/script_block.dart
/*
  This file is part of LONCAPA-Daxe.

  LONCAPA-Daxe 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 3 of the License, or
  (at your option) any later version.

  LONCAPA-Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of loncapa_daxe;

/**
 * Perl block display for LON-CAPA
 * Jaxe display type: 'script'.
 */
class ScriptBlock extends LCDBlock {
  
  static List<String> keywords = [
    'if', 'unless', 'else', 'elsif', 'while', 'until', 'for', 'each', 'foreach', 'next',
    'last', 'break', 'continue', 'return', 'my', 'our', 'local', 'state', 'BEGIN', 'END',
    'package', 'sub', 'do', 'given ', 'when ', 'default', '__END__', '__DATA__',
    '__FILE__', '__LINE__', '__PACKAGE__'];
  
  ScriptBlock.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
  }
  
  ScriptBlock.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
    if (firstChild is DNText && firstChild.nextSibling == null) {
      firstChild.replaceWith(new ParentUpdatingDNText(firstChild.nodeValue));
    }
  }
  
  @override
  h.Element html() {
    h.Element div = super.html();
    if (firstChild == null || firstChild.nextSibling != null || firstChild is! ParentUpdatingDNText)
      return div;
    if (state != 2 && hasContent) {
      h.DivElement contents = div.lastChild;
      if (getAttribute('type') == 'loncapa/perl') {
        contents.classes.add('perl-text');
        h.DivElement overlay = createPerlOverlay();
        contents.style.position = 'relative';
        overlay.style.position = 'absolute';
        overlay.style.top = '0px';
        overlay.style.left = '0px';
        contents.append(overlay);
      }
    }
    return div;
  }
  
  h.Element createPerlOverlay() {
    // NOTE: this is very basic, we might need a real parser to do something more complex
    // TODO: add special highlighting for string redirect << and regular expressions 
    final String letters = '\$@%&abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_'; // starting chars in names
    final String digits = '0123456789';
    h.DivElement div = new h.DivElement();
    div.classes.add('perl-colored');
    String text = firstChild.nodeValue;
    StringBuffer sb = new StringBuffer();
    bool in_name = false;
    bool in_comment = false;
    bool in_number = false;
    bool in_string = false;
    bool in_backslash = false;
    String string_start;
    int i = 0;
    while (i < text.length) {
      String c = text[i];
      if (!in_string && !in_comment && (letters.contains(c) || in_name && digits.contains(c) ||
          in_name && c == '#' && sb.toString()[sb.length-1] == '\$')) {
        if (!in_name ) {
          if (sb.length > 0) {
            div.append(new h.Text(sb.toString()));
            sb.clear();
          }
          in_name = true;
        }
      } else if (!in_string && !in_name && !in_comment && (digits.contains(c) || (c == '.' && in_number))) {
        if (!in_number) {
          if (sb.length > 0) {
            div.append(new h.Text(sb.toString()));
            sb.clear();
          }
          in_number = true;
        }
      } else {
        if (in_name) {
          String s = sb.toString();
          h.SpanElement span = new h.SpanElement();
          if (keywords.contains(s))
            span.classes.add('keyword');
          else if (s.startsWith('\$') || s.startsWith('@') || s.startsWith('%'))
            span.classes.add('variable');
          else if (s.startsWith('&') || c == '(')
            span.classes.add('function-call');
          else
            span.classes.add('name');
          span.appendText(s);
          div.append(span);
          sb.clear();
          in_name = false;
        } else if (in_number) {
          h.SpanElement span = new h.SpanElement();
          span.classes.add('number');
          span.appendText(sb.toString());
          div.append(span);
          sb.clear();
          in_number = false;
        } else if (in_comment && (c == '\n' || c == '\r')) {
          h.SpanElement span = new h.SpanElement();
          span.classes.add('comment');
          span.appendText(sb.toString());
          div.append(span);
          sb.clear();
          in_comment = false;
        }
        if (!in_comment && (c == '"' || c == "'") && !in_backslash) {
          if (in_string) {
            if (c == string_start) {
              div.append(new h.Text(string_start));
              h.SpanElement span = new h.SpanElement();
              span.classes.add('string');
              span.appendText(sb.toString().substring(1));
              div.append(span);
              sb.clear();
              in_string = false;
            }
          } else {
            if (sb.length > 0) {
              div.append(new h.Text(sb.toString()));
              sb.clear();
            }
            string_start = c;
            in_string = true;
          }
        } else if (!in_string && c == '#') {
          if (sb.length > 0) {
            div.append(new h.Text(sb.toString()));
            sb.clear();
          }
          in_comment = true;
        }
      }
      if (in_string) {
        if (c == '\\')
          in_backslash = !in_backslash;
        else if (in_backslash)
          in_backslash = false;
      }
      sb.write(c);
      i++;
    }
    if (sb.length > 0) {
      if (in_comment) {
        h.SpanElement span = new h.SpanElement();
        span.classes.add('comment');
        span.appendText(sb.toString());
        div.append(span);
      } else
        div.append(new h.Text(sb.toString()));
    }
    return(div);
  }
  
  bool get needsParentUpdatingDNText {
    return true;
  }
}

Index: modules/damieng/graphical_editor/loncapa_daxe/web/nodes/simple_ui_text.dart
+++ modules/damieng/graphical_editor/loncapa_daxe/web/nodes/simple_ui_text.dart
/*
  This file is part of LONCAPA-Daxe.

  LONCAPA-Daxe 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 3 of the License, or
  (at your option) any later version.

  LONCAPA-Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of loncapa_daxe;

class SimpleUIText {
  
  static List<String> possibleDescendants = ['m','lm','chem','num','table','tr','td','ul','ol','li',
    'a','span','p','b','i','sup','sub','section'];

  /**
   * Returns true if the given node only contains elements that can be displayed
   * with simple UI as part of the text.
   */
  static bool checkNodeContents(DaxeNode node) {
    for (DaxeNode dn=node.firstChild; dn!= null; dn=dn.nextSibling) {
      if (!SimpleUIText.checkNode(dn))
        return false;
    }
    return true;
  }
  
  /**
   * Returns true if the given node can be displayed with simple UI as part of the text.
   */
  static bool checkNode(DaxeNode node) {
    if (node.nodeType == DaxeNode.TEXT_NODE)
      return true;
    if (!possibleDescendants.contains(node.nodeName))
      return false;
    if (node.attributes.length != 0)
      return false; // TODO: add some attributes
    return SimpleUIText.checkNodeContents(node);
  }
}

Index: modules/damieng/graphical_editor/loncapa_daxe/web/nodes/tex_mathjax.dart
+++ modules/damieng/graphical_editor/loncapa_daxe/web/nodes/tex_mathjax.dart
/*
  This file is part of LONCAPA-Daxe.

  LONCAPA-Daxe 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 3 of the License, or
  (at your option) any later version.

  LONCAPA-Daxe 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 Daxe.  If not, see <http://www.gnu.org/licenses/>.
*/

part of loncapa_daxe;

/**
 * Displays tm and dtm elements (LaTeX inline and display math) with MathJax.
 * Jaxe display type: 'texmathjax'.
 */
class TeXMathJax extends DaxeNode {
  
  TeXMathJax.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
  }
  
  TeXMathJax.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
  }
  
  @override
  h.Element html() {
    h.SpanElement span = new h.SpanElement();
    span.id = "$id";
    span.classes.add('dn');
    if (!valid)
      span.classes.add('invalid');
    span.classes.add('tex');
    span.onClick.listen((h.MouseEvent event) => editDialog(() => updateEquationDisplay()));
    return(span);
  }
  
  @override
  void newNodeCreationUI(ActionFunction okfct) {
    editDialog(() => okfct());
  }
  
  @override
  Position firstCursorPositionInside() {
    return(null);
  }
  
  @override
  Position lastCursorPositionInside() {
    return(null);
  }
  
  @override
  void afterInsert() {
    updateEquationDisplay();
  }
  
  void updateEquationDisplay() {
    String equationText = '?';
    if (firstChild != null && firstChild.nodeValue.trim() != '')
      equationText = firstChild.nodeValue;
    js.JsObject queue = js.context['MathJax']['Hub']['Queue'];
    h.SpanElement span = h.document.getElementById(id);
    span.text = convertForMathJaxe(equationText);
    js.JsArray params = new js.JsObject.jsify( ['Typeset', js.context['MathJax']['Hub'], id] );
    js.context['MathJax']['Hub'].callMethod('Queue', [params]);
  }
  
  void editDialog([ActionFunction okfct]) {
    TeXMathJaxeDialog dlg = new TeXMathJaxeDialog(this, okfct);
    dlg.show();
  }
  
  String convertForMathJaxe(String text) {
    bool hasDelimiters = false;
    text = text.trim();
    if (text.length > 3 && text.startsWith('\$\$') && text.endsWith('\$\$')) {
      text = "\\[${text.substring(2,text.length-2)}\\]";
      hasDelimiters = true;
    } else if (text.length > 1 && text.startsWith('\$') && text.endsWith('\$')) {
      text = "\\(${text.substring(1,text.length-1)}\\)";
      hasDelimiters = true;
    }
    if (hasDelimiters) {
      // turn Perl variables to tt style
      text = text.replaceAllMapped(new RegExp(r'\$[a-zA-Z]*[0-9]*(\[[^\]]*\])?'), (match) {
        return "\\mathtt{${match.group(0)}}";
      });
    }
    return(text);
  }
}


class TeXMathJaxeDialog {
  TeXMathJax dn;
  ActionFunction _okfct;
  TeXMathJaxeDialog(this.dn, [this._okfct]) {
  }
  
  void show() {
    h.DivElement div1 = new h.DivElement();
    div1.id = 'dlg1';
    div1.classes.add('dlg1');
    h.DivElement div2 = new h.DivElement();
    div2.classes.add('dlg2');
    h.DivElement div3 = new h.DivElement();
    div3.classes.add('dlg3');
    h.FormElement form = new h.FormElement();
    
    h.TextAreaElement ta = new h.TextAreaElement();
    ta.id = 'eqtext';
    if (dn.firstChild != null)
      ta.value = dn.firstChild.nodeValue.trim();
    ta.style.width = '100%';
    ta.style.height = '4em';
    ta.attributes['spellcheck'] = 'false';
    ta.onInput.listen((h.Event event) => input());
    form.append(ta);
    
    h.DivElement preview = new h.DivElement();
    preview.id = 'preview';
    form.append(preview);
    
    h.DivElement div_buttons = new h.DivElement();
    div_buttons.classes.add('buttons');
    h.ButtonElement bCancel = new h.ButtonElement();
    bCancel.attributes['type'] = 'button';
    bCancel.appendText(Strings.get("button.Cancel"));
    bCancel.onClick.listen((h.MouseEvent event) => div1.remove());
    div_buttons.append(bCancel);
    h.ButtonElement bOk = new h.ButtonElement();
    bOk.attributes['type'] = 'submit';
    bOk.appendText(Strings.get("button.OK"));
    bOk.onClick.listen((h.MouseEvent event) => ok(event));
    div_buttons.append(bOk);
    form.append(div_buttons);
    
    div3.append(form);
    div2.append(div3);
    div1.append(div2);
    h.document.body.append(div1);
    
    ta.focus();
    input();
  }
  
  void ok(h.MouseEvent event) {
    h.TextAreaElement ta = h.querySelector('textarea#eqtext');
    String equationText = ta.value;
    if (equationText != '') {
      if (dn.firstChild != null)
        dn.firstChild.nodeValue = equationText;
      else
        dn.appendChild(new DNText(equationText));
    } else {
      if (dn.firstChild != null)
        dn.removeChild(dn.firstChild);
    }
    h.querySelector('div#dlg1').remove();
    if (event != null)
      event.preventDefault();
    if (_okfct != null)
      _okfct();
  }
  
  void input() {
    h.TextAreaElement ta = h.querySelector('textarea#eqtext');
    String text = ta.value;
    /* newlines allowed -> must use button to insert equations
    if (text.length > 0 && text.contains('\n')) {
      ta.value = text.replaceAll('\n', '');
      ok(null);
      return;
    }
    */
    h.DivElement previewDiv = h.document.getElementById('preview');
    /* this does not work bec. the initial text has not been processed before
    js.JsObject queue = js.context['MathJax']['Hub']['queue'];
    //math = MathJax.Hub.getAllJax("MathOutput")[0]
    js.JsArray all = js.context['MathJax']['Hub'].callMethod('getAllJax', ['preview']);
    js.JsObject math = all[0];
    // MathJax.Hub.queue.Push(['Text', math, "\\displaystyle{$text}"]);
    js.JsObject params = new js.JsObject.jsify(['Text', math, "\\displaystyle{$text}"]);
    queue.callMethod('Push', [params]);
    */
    previewDiv.text = dn.convertForMathJaxe(text);
    js.JsArray params = new js.JsObject.jsify( ['Typeset', js.context['MathJax']['Hub'], 'preview'] );
    js.context['MathJax']['Hub'].callMethod('Queue', [params]);
    
  }
}

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/ClickImageExample.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/ClickImageExample.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <p>Consider the image below.</p>
  <imageresponse max="1">
    <foilgroup>
      <foil name="foil1">
        <image>/res/adm/includes/templates/man1.jpg</image>
        <text>Click on an eye</text>
        <rectangle>(127,139)-(186,178)</rectangle>
        <rectangle>(242,139)-(306,183)</rectangle>
      </foil>
      <foil name="foil2">
        <image>/res/adm/includes/templates/man1.jpg</image>
        <text>Click on the nose</text>
        <rectangle>(192,180)-(227,222)</rectangle>
      </foil>
      <foil name="foil3">
        <image>/res/adm/includes/templates/man1.jpg</image>
        <text>Click on the mouth</text>
        <rectangle>(142,226)-(294,293)</rectangle>
      </foil>
    </foilgroup>
    <hintgroup>
      This is a hint.
    </hintgroup>
  </imageresponse>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/CustomResponse.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/CustomResponse.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <p>Accept an answer of around 90 or -90
    <customresponse answerdisplay="something near 90 or -90">
      <answer type="loncapa/perl"><![CDATA[
# We do not want a vector
if ($submission=~/\,/) { return 'EXTRA_ANSWER'; }
# No units needed
if ($submission=~/^\d+\s+\w+$/) { return 'UNIT_NOTNEEDED'; }
# Need a numerical answer here
if ($submission!~/^[\d\.\-]+$/) { return 'WANTED_NUMERIC'; }
$difference=abs(90-abs($submission));
if ($difference==0) { return 'EXACT_ANS'; }
if ($difference < 0.001) { return 'APPROX_ANS'; }
return 'INCORRECT';
]]></answer>
      <textline/>
    </customresponse></p>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/DropBox.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/DropBox.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <parameter name="scoreformat" description="Format for display of score" type="string"/>
  <parameter name="uploadedfiletypes" id="11" default="doc,docx,xls,xlsx,ppt,pptx,png,jpg,jpeg,gif,txt,pdf,htm,html" type="string_fileext" description="Allowed File Extensions for Uploaded Files"/>
  <parameter name="maxfilesize" id="13" default="10.0" type="float_pos" description="Max. cumulative size (MB) for submitted file(s)"/>
  <script type="loncapa/perl"><![CDATA[
$weight=&parameter_setting('weight');
if ((!defined($weight)) || ($weight eq '')) { $weight=1; }
$awarded=&stored_data('awarded');
$scoreformat=&parameter_setting('scoreformat');
if (!defined($scoreformat) || $scoreformat eq '') { $scoreformat="2f"; }
$display='';
if (&parameter_setting('problemstatus')!~/^no/) {
   if (!defined($awarded)) {
      $display=$weight.' possible points.';
   } else {
      $display='You have '.&format($awarded*$weight,$scoreformat).' out of '.
            $weight.' possible points.';
   }
}
$comment=&stored_data('comment');
if (!defined($comment) || $comment!~/\w/) {
   $comment='';
} else {
   $comment='<br /><table><tr><td bgcolor="#FFFFDD">'.$comment.'</td></tr></table>';
}
$gradeinfo=&stored_data('gradeinfo');
if (!defined($gradeinfo) || $gradeinfo!~/\w/) {
   $gradeinfo='';
} else {
   $gradeinfo='<br /><table><tr><td bgcolor="#DDDDFF"><font size="+2">'.$gradeinfo.'</font></td></tr></table>';
}
]]></script>
  <displaytitle/>
  <p>$display</p>
  <instructorcomment>
    <p/>
    <p><span class="LC_warning">You should adjust this resource in "Content Settings "</span></p>
    <ul>
      <li class="LC_warning">You can modify the rounding of the score display using the "scoreformat" setting.</li>
      <li class="LC_warning">To change the allowed file types for submissions, change the "uploadedfiletypes"</li>
      <li class="LC_warning">To change the maximum cumulative size for submitted files for this item, modify the "maxfilesize" parameter (The default is 10 MB).</li>
      <li class="LC_warning">Feedback on grades can be controlled using the "problemstatus" parameter.</li>
    </ul>
    <p>You can upload comments and grade information with this resource when uploading CSV files.</p>
  </instructorcomment>
  <p>$gradeinfo
    $comment</p>
  <essayresponse id="upload">
  </essayresponse>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/Essay.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/Essay.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <p>Enter the question text here. Type in an essay below.</p>
  <essayresponse>
    <textfield/>
  </essayresponse>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/HintFormula.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/HintFormula.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <script type="loncapa/perl"><![CDATA[
$a1=&random(3,6,1);
$e1=&random(3,6,1);
$a2=&random(6,8,1);
$e2=&random(6,8,1);
$da1=$a1*$e1;
$da2=$a2*$e2;
$de1=$e1-1;
$de2=$e2-1;
@derivative=($da1.'t^'.$de1,$da2.'t^'.$de2);
@wrongderivative=($a1.'t^'.$de1,$a2.'t^'.$de2);
]]></script>
  <p>What is the derivative of</p>
  <m eval="on">$$
    \left(\begin{array}{c}$a1 t^$e1\\ $a2 t^$e2\end{array}\right)
  $$</m>
  <p>with respect to <m>$ t $</m>?
    <formularesponse answer="@derivative">
      <textline readonly="no" size="25"/>
      <hintgroup showoncorrect="no">
        <formulahint answer="@wrongderivative" name="nomulti"/>
        <hintpart on="nomulti">
          You need to multiply with the original exponent.
        </hintpart>
      </hintgroup>
    </formularesponse></p>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/HintMathResponse.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/HintMathResponse.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <script type="loncapa/perl"><![CDATA[
$a1 = &random(-6,6,4);
$a2 = &random(-6,6,4);
$n1 = &random(3,11,2);
$n2 = &random(2,10,2);
$function = "$a1*cos($n1*x)+$a2*sin($n2*x)";
$example=&xmlparse('An example would be <m eval="on">$ (sin($n1\cdot x)+cos($n2\cdot x))/\sqrt{2} $</m>');
]]></script>
  <p>Give an example of a function</p>
  <ol>
    <li><p>which is orthogonal to</p>
      <p style="text-align: center"><algebra>$function</algebra></p>
      <p/>
      <p>with respect to the scalar product</p>
      <m>$$
        <g \mid h> = \frac{1}{\pi} \int_{-\pi}^{\pi}dx g(x) \cdot h(x)
      $$</m></li>
    <li>whose norm is 1.</li>
  </ol>
  <mathresponse answerdisplay="$example" cas="maxima" args="$function">
    <answer><![CDATA[
overlap:integrate((RESPONSE[1])*(LONCAPALIST[1]),x,-%pi,%pi)/%pi;
norm:integrate((RESPONSE[1])*(RESPONSE[1]),x,-%pi,%pi)/%pi;
is(overlap=0 and norm=1);
]]></answer>
    <textline readonly="no" size="50"/>
    <hintgroup showoncorrect="no">
      <mathhint name="ortho" args="$function" cas="maxima">
        <answer><![CDATA[
overlap: integrate((LONCAPALIST[1])*(RESPONSE[1]),x,-%pi,%pi)/%pi;
is(not overlap = 0);
]]></answer>
      </mathhint>
      <mathhint name="norm" args="$function" cas="maxima">
        <answer><![CDATA[
norm: integrate((RESPONSE[1])*(RESPONSE[1]),x,-%pi,%pi)/%pi;
is(not norm = 1);
]]></answer>
      </mathhint>
      <hintpart on="norm">
        The function you have provided does not have a norm of one.
      </hintpart>
      <hintpart on="ortho">
        The function you have provided is not orthogonal.
      </hintpart>
    </hintgroup>
  </mathresponse>
  <postanswerdate>
    <p>Note that with respect to the above norm, <m>$ \cos(nx) $</m> is perpendicular to <m>$ \sin(nx) $</m> and perpendicular to <m>$ \cos(mx) $</m> for <m>$ n\ne m $</m>.</p>
  </postanswerdate>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/MultipleAnswerEither.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/MultipleAnswerEither.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <script type="loncapa/perl"><![CDATA[
#Enter the computations here
$a=&random(1,10,1);
$b=$a*$a;
]]></script>
  <p>If <m eval="on">$ x^2=$b $</m>, what is a possible value of <m>$ x $</m>?
    <numericalresponse>
      <answergroup type="ordered">
        <answer name="negative" type="ordered">
<value>-$a</value>
        </answer>
        <answer name="positive" type="ordered">
<value>$a</value>
        </answer>
      </answergroup>
      <responseparam type="tolerance" default="5%" name="tol" description="Numerical Tolerance"/>
      <responseparam name="sig" type="int_range,0-16" default="0,15" description="Significant Figures"/>
      <textline readonly="no"/>
    </numericalresponse></p>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/MultipleAnswerUnordered.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/MultipleAnswerUnordered.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <script type="loncapa/perl"><![CDATA[
$a = &random(1,7,1);
$b = $a*$a;
]]></script>
  <p>What are the factors of
    <m eval="on">$x^2 - $b$</m>?
    <formularesponse>
      <answergroup type="ordered">
        <answer name="both" type="unordered">
<value>x-$a</value><value>x+$a</value>
        </answer>
      </answergroup>
      <textline readonly="no" size="10"/>
      <textline readonly="no" size="10"/>
    </formularesponse></p>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/Plot_curve.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/Plot_curve.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <script type="loncapa/perl"><![CDATA[
$amplitude=&random(0.5,3.5,0.5);
$omega=&random(1,2,0.5);
$function="$amplitude*sin($omega*x)";
]]></script>
  <gnuplot width="300" transparent="off" samples="100" grid="on" font="9" bgcolor="xffffff" height="300" align="left" fgcolor="x000000" border="on" plottype="Cartesian">
    <axis xmin="-5" ymin="-4" xmax="5" ymax="4" color="x000000"/>
    <xlabel>Label X</xlabel>
    <ylabel>Label Y</ylabel>
    <curve linestyle="linespoints" name="My Plot" pointtype="0" color="x000000">
      <function>$function</function>
    </curve>
  </gnuplot>
  <p>What is the amplitude of this function?
    <numericalresponse answer="$amplitude" format="2s">
      <responseparam name="tol" type="tolerance" description="Numerical Tolerance" default="5%"/>
      <textline readonly="no"/>
    </numericalresponse></p>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/Plot_data.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/Plot_data.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <script type="loncapa/perl"><![CDATA[
#
# This example populates arrays with the x and y coordinates of the plot.
# For continuous functions, another approach is to use curve plots.
#
$amplitude = &random(1,4,0.5);
$x_min = -5;
$x_max =  5;
for ($x=$x_min;$x<=$x_max;$x=$x+0.05) {
   push(@X,$x);
   push(@Y,$amplitude*sin($x));
# Safeguard:
# The following line limits the size of the array to 1000 to avoid infinite loops
   if (($#X>1000) || ($#Y>1000)) { last; }
}
]]></script>
  <gnuplot width="300" transparent="off" samples="100" grid="on" font="9" bgcolor="xffffff" height="300" align="left" fgcolor="x000000" border="on" plottype="Cartesian">
    <axis xmin="$x_min" ymin="-5" xmax="$x_max" ymax="5" color="x000000"/>
    <xlabel>Label X</xlabel>
    <ylabel>Label Y</ylabel>
    <curve linestyle="linespoints" name="My Plot" pointtype="0" color="x000000">
      <data>@X</data>
      <data>@Y</data>
    </curve>
  </gnuplot>
  <p>What is the amplitude of this function?
    <numericalresponse answer="$amplitude" format="2s">
      <responseparam name="tol" type="tolerance" description="Numerical Tolerance" default="5%"/>
      <textline readonly="no"/>
    </numericalresponse></p>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/RadioResponse.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/RadioResponse.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <parameter name="maxtries" type="int_pos" default="2" description="Maximum Number of Tries"/>
  <p>Enter the text of the question here.</p>
  <radiobuttonresponse max="10" randomize="yes">
    <foilgroup>
      <foil location="random" value="true" name="foil1">
        This is foil One, and it is currently set to "true."  Only one true foil is selected.
      </foil>
      <foil location="random" value="true" name="foil2">
        This is foil Two, and it is currently set to "true."  Only one true foil is selected.
      </foil>
      <foil location="random" value="false" name="foil3">
        This is foil Three, and it is currently set to "false."
      </foil>
      <foil location="random" value="false" name="foil4">
        This is foil Four, and it is currently set to "false."
      </foil>
      <foil location="random" value="false" name="foil5">
        This is foil Five, and it is currently set to "false."
      </foil>
    </foilgroup>
  </radiobuttonresponse>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/RandomLabelExample.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/RandomLabelExample.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <randomlabel width="576" height="432" texwidth="60" bgimg="/res/adm/includes/templates/man1.jpg">
    <labelgroup name="face" type="text">
      <location x="73" y="60" value=""/>
      <location x="443" y="169" value=""/>
      <location x="46" y="263" value=""/>
      <location x="364" y="332" value=""/>
      <label>A</label>
      <label>B</label>
      <label>C</label>
      <label>D</label>
    </labelgroup>
  </randomlabel>
  <p>Match the following:
    <optionresponse max="10">
      <foilgroup options="('A', 'B', 'C', 'D')">
        <foil name="eyebrow" value="$face{1}">
          eyebrow
        </foil>
        <foil name="eye" value="$face{2}">
          eye
        </foil>
        <foil name="nose" value="$face{3}">
          nose
        </foil>
        <foil name="mouth" value="$face{4}">
          mouth
        </foil>
      </foilgroup>
      <hintgroup>
      </hintgroup>
    </optionresponse></p>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/Rnumerical.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/Rnumerical.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <script type="loncapa/perl"><![CDATA[
$seed=&random(1,500,1);
$n=&random(15,25,1);
$offset=&random(2,5,0.1);
$slope=&random(0.6,2.5,0.1);
# construct a data set using R
# dump is for debugging, print to screen to see data structure
($data,$dump)=&cas_hashref('R',"set.seed($seed);x<-1:$n;w<-1+sqrt(x)/2;data.frame(x=x,y=$offset+$slope*x+rnorm(x)*w);");
@x=&cas_hashref_array($data,'x');
@y=&cas_hashref_array($data,'y');
$datax=join(',', at x);
$datay=join(',', at y);
# calculate the right answer using R
($answerdata,$dump)=&cas_hashref('R',"x<-c($datax);y<-c($datay);dataset<-data.frame(x=x,y=y);fm<-lm(y~x,data=dataset);");
$answer=&cas_hashref_entry($answerdata,'coefficients','x').'*x+'.&cas_hashref_entry($answerdata,'coefficients','(Intercept)');
]]></script>
  <p>Consider the plotted data set.</p>
  <gnuplot width="400" solid="0" plotcolor="monochrome" gridlayer="off" bmargin="default" font="9" alttag="dynamically generated plot" bgcolor="xffffff" texfont="22" transparent="off" plottype="Cartesian" rmargin="default" gridtype="Cartesian" minor_ticscale="0.5" fontface="sans-serif" grid="on" align="left" texwidth="93" height="300" border="on" samples="100" fgcolor="x000000" major_ticscale="1" tmargin="default" lmargin="default" fillstyle="empty">
    <curve linestyle="points" linetype="solid" color="x000000" pointtype="3" limit="closed" pointsize="2" linewidth="1">
      <data>@x</data>
      <data>@y</data>
    </curve>
  </gnuplot>
  <p/>
  <p>Give a linear function approximating the data.</p>
  <p><tt>y(x)=</tt>
    <formularesponse id="11" answer="$answer" samples="x at 1:$n#20">
      <responseparam name="tol" default="2%" description="Numerical Tolerance" type="tolerance"/>
      <textline size="25" readonly="no"/>
    </formularesponse></p>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/SelectFromOptions-4ConceptGoups.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/SelectFromOptions-4ConceptGoups.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <p>Type Question Here.
    <optionresponse max="10">
      <foilgroup options="('Green','Red')">
        <conceptgroup concept="One">
          <foil name="OneA" value="Green">
            This is statement OneA of concept One.  Green
          </foil>
          <foil name="OneB" value="Red">
            This is statement OneB of concept One.  Red
          </foil>
          <foil name="OneC" value="Green">
            This is statement OneC of concept One.  Green
          </foil>
        </conceptgroup>
        <conceptgroup concept="Two">
          <foil name="TwoA" value="Green">
            This is statement TwoA of concept Two.  Green
          </foil>
          <foil name="TwoB" value="Red">
            This is statement TwoB of concept Two.  Red
          </foil>
        </conceptgroup>
        <conceptgroup concept="Three">
          <foil name="ThreeA" value="Green">
            This is statement ThreeA of concept Three.  Green
          </foil>
          <foil name="ThreeB" value="Red">
            This is statement ThreeB of concept Three.  Red
          </foil>
        </conceptgroup>
        <conceptgroup concept="Four">
          <foil name="FourA" value="Green">
            This is statement FourA of concept Four.  Green
          </foil>
          <foil name="FourB" value="Red">
            This is statement FourB of concept Four.  Red
          </foil>
        </conceptgroup>
      </foilgroup>
      <hintgroup>
        Add hint text here.
      </hintgroup>
    </optionresponse></p>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/SelectFromOptions-5ConceptGoups.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/SelectFromOptions-5ConceptGoups.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <p>Type Question Here.
    <optionresponse max="10">
      <foilgroup options="('True','False')">
        <conceptgroup concept="One">
          <foil name="OneA" value="True">
            This is statement OneA of concept One.  True
          </foil>
          <foil name="OneB" value="False">
            This is statement OneB of concept One.  False
          </foil>
          <foil name="OneC" value="True">
            This is statement OneC of concept One.  True
          </foil>
        </conceptgroup>
        <conceptgroup concept="Two">
          <foil name="TwoA" value="True">
            This is statement TwoA of concept Two.  True
          </foil>
          <foil name="TwoB" value="False">
            This is statement TwoB of concept Two.  False
          </foil>
        </conceptgroup>
        <conceptgroup concept="Three">
          <foil name="ThreeA" value="True">
            This is statement ThreeA of concept Three.  True
          </foil>
          <foil name="ThreeB" value="False">
            This is statement ThreeB of concept Three.  False
          </foil>
        </conceptgroup>
        <conceptgroup concept="Four">
          <foil name="FourA" value="True">
            This is statement FourA of concept Four.  True
          </foil>
          <foil name="FourB" value="False">
            This is statement FourB of concept Four.  False
          </foil>
        </conceptgroup>
        <conceptgroup concept="Five">
          <foil name="FiveA" value="True">
            This is statement FiveA of concept Five.  True
          </foil>
          <foil name="FiveB" value="False">
            This is statement FiveB of concept Five.  False
          </foil>
        </conceptgroup>
      </foilgroup>
      <hintgroup>
        Add hint text here.
      </hintgroup>
    </optionresponse></p>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/SelectFromOptions-6ConceptGoups.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/SelectFromOptions-6ConceptGoups.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <p>Type Question Here.
    <optionresponse max="10">
      <foilgroup options="('True','False')">
        <conceptgroup concept="One">
          <foil name="OneA" value="True">
            This is statement OneA of concept One.  True
          </foil>
          <foil name="OneB" value="False">
            This is statement OneB of concept One.  False
          </foil>
          <foil name="OneC" value="True">
            This is statement OneC of concept One.  True
          </foil>
        </conceptgroup>
        <conceptgroup concept="Two">
          <foil name="TwoA" value="True">
            This is statement TwoA of concept Two.  True
          </foil>
          <foil name="TwoB" value="False">
            This is statement TwoB of concept Two.  False
          </foil>
        </conceptgroup>
        <conceptgroup concept="Three">
          <foil name="ThreeA" value="True">
            This is statement ThreeA of concept Three.  True
          </foil>
          <foil name="ThreeB" value="False">
            This is statement ThreeB of concept Three.  False
          </foil>
        </conceptgroup>
        <conceptgroup concept="Four">
          <foil name="FourA" value="True">
            This is statement FourA of concept Four.  True
          </foil>
          <foil name="FourB" value="False">
            This is statement FourB of concept Four.  False
          </foil>
        </conceptgroup>
        <conceptgroup concept="Five">
          <foil name="FiveA" value="True">
            This is statement FiveA of concept Five.  True
          </foil>
          <foil name="FiveB" value="False">
            This is statement FiveB of concept Five.  False
          </foil>
        </conceptgroup>
        <conceptgroup concept="Six">
          <foil name="SixA" value="True">
            This is statement SixA of concept Six.  True
          </foil>
          <foil name="SixB" value="False">
            This is statement SixB of concept Six.  False
          </foil>
        </conceptgroup>
      </foilgroup>
      <hintgroup>
        Add hint text here.
      </hintgroup>
    </optionresponse></p>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/SelectFromOptions-7ConceptGoups.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/SelectFromOptions-7ConceptGoups.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <p>Type Question Here.
    <optionresponse max="10">
      <foilgroup options="('True','False')">
        <conceptgroup concept="One">
          <foil name="OneA" value="True">
            This is statement OneA of concept One.  True
          </foil>
          <foil name="OneB" value="False">
            This is statement OneB of concept One.  False
          </foil>
          <foil name="OneC" value="True">
            This is statement OneC of concept One.  True
          </foil>
        </conceptgroup>
        <conceptgroup concept="Two">
          <foil name="TwoA" value="True">
            This is statement TwoA of concept Two.  True
          </foil>
          <foil name="TwoB" value="False">
            This is statement TwoB of concept Two.  False
          </foil>
        </conceptgroup>
        <conceptgroup concept="Three">
          <foil name="ThreeA" value="True">
            This is statement ThreeA of concept Three.  True
          </foil>
          <foil name="ThreeB" value="False">
            This is statement ThreeB of concept Three.  False
          </foil>
        </conceptgroup>
        <conceptgroup concept="Four">
          <foil name="FourA" value="True">
            This is statement FourA of concept Four.  True
          </foil>
          <foil name="FourB" value="False">
            This is statement FourB of concept Four.  False
          </foil>
        </conceptgroup>
        <conceptgroup concept="Five">
          <foil name="FiveA" value="True">
            This is statement FiveA of concept Five.  True
          </foil>
          <foil name="FiveB" value="False">
            This is statement FiveB of concept Five.  False
          </foil>
        </conceptgroup>
        <conceptgroup concept="Six">
          <foil name="SixA" value="True">
            This is statement SixA of concept Six.  True
          </foil>
          <foil name="SixB" value="False">
            This is statement SixB of concept Six.  False
          </foil>
        </conceptgroup>
        <conceptgroup concept="Seven">
          <foil name="SevenA" value="True">
            This is statement SevenA of concept Seven.  True
          </foil>
          <foil name="SevenB" value="False">
            This is statement SixB of concept Six.  False
          </foil>
        </conceptgroup>
      </foilgroup>
      <hintgroup>
        Add hint text here.
      </hintgroup>
    </optionresponse></p>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/SelectFromOptions-8ConceptGoups.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/SelectFromOptions-8ConceptGoups.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <p>Type Question Here.
    <optionresponse max="10">
      <foilgroup options="('True','False')">
        <conceptgroup concept="One">
          <foil name="OneA" value="True">
            This is statement OneA of concept One.  True
          </foil>
          <foil name="OneB" value="False">
            This is statement OneB of concept One.  False
          </foil>
          <foil name="OneC" value="True">
            This is statement OneC of concept One.  True
          </foil>
        </conceptgroup>
        <conceptgroup concept="Two">
          <foil name="TwoA" value="True">
            This is statement TwoA of concept Two.  True
          </foil>
          <foil name="TwoB" value="False">
            This is statement TwoB of concept Two.  False
          </foil>
        </conceptgroup>
        <conceptgroup concept="Three">
          <foil name="ThreeA" value="True">
            This is statement ThreeA of concept Three.  True
          </foil>
          <foil name="ThreeB" value="False">
            This is statement ThreeB of concept Three.  False
          </foil>
        </conceptgroup>
        <conceptgroup concept="Four">
          <foil name="FourA" value="True">
            This is statement FourA of concept Four.  True
          </foil>
          <foil name="FourB" value="False">
            This is statement FourB of concept Four.  False
          </foil>
        </conceptgroup>
        <conceptgroup concept="Five">
          <foil name="FiveA" value="True">
            This is statement FiveA of concept Five.  True
          </foil>
          <foil name="FiveB" value="False">
            This is statement FiveB of concept Five.  False
          </foil>
        </conceptgroup>
        <conceptgroup concept="Six">
          <foil name="SixA" value="True">
            This is statement SixA of concept Six.  True
          </foil>
          <foil name="SixB" value="False">
            This is statement SixB of concept Six.  False
          </foil>
        </conceptgroup>
        <conceptgroup concept="Seven">
          <foil name="SevenA" value="True">
            This is statement SevenA of concept Seven.  True
          </foil>
          <foil name="SevenB" value="False">
            This is statement SixB of concept Six.  False
          </foil>
        </conceptgroup>
        <conceptgroup concept="Eight">
          <foil name="EightA" value="True">
            This is statement EightA of concept Eight.  True
          </foil>
          <foil name="EightB" value="False">
            This is statement EightB of concept Eight.  False
          </foil>
        </conceptgroup>
      </foilgroup>
      <hintgroup>
        Add hint text here.
      </hintgroup>
    </optionresponse></p>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/SelectFromOptions-Simple.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/SelectFromOptions-Simple.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <p>Type Question Here.
    <optionresponse max="10" randomize="yes">
      <foilgroup options="('Green','Red','Blue')">
        <foil location="random" value="Green" name="Foil1">
          This is foil One. It is currently set to "Green".
        </foil>
        <foil location="random" value="Red" name="Foil2">
          This is foil Two. It is currently set to "Red".
        </foil>
        <foil location="random" value="Blue" name="Foil3">
          This is foil Three. It is currently set to "Blue".
        </foil>
        <foil location="random" value="Red" name="Foil4">
          This is foil Four. It is currently set to "Red".
        </foil>
      </foilgroup>
      <hintgroup>
      </hintgroup>
    </optionresponse></p>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/SelectFromOptions-multilingual.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/SelectFromOptions-multilingual.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <script type="loncapa/perl"><![CDATA[
$bestlanguage = &languages(['en','de','fr']);
if ($bestlanguage eq 'de') {
    %colors = (green => 'Grün', red => 'Rot', blue => 'Blau');
} elsif ($bestlanguage eq 'fr') {
    %colors = (green => 'Vert', red => 'Rouge', blue => 'Bleu');
} else { # 'en' and default
    %colors = (green => 'Green', red => 'Red', blue => 'Blue');
}
]]></script>
  
  <translated>
    <lang which="en">
      <p>English Question</p>
    </lang>
    <lang which="de">
      <p>Deutscher Aufgabentext</p>
    </lang>
    <lang which="fr">
      <p>Texte de la question en français</p>
    </lang>
    <lang which="default">
      <p>English Question</p>
    </lang>
  </translated>
  
  <optionresponse max="10" randomize="yes" TeXlayout="horizontal">
    <foilgroup options="('$colors{red}','$colors{green}','$colors{blue}')">
      <foil location="random" value="$colors{green}" name="Foil1">
        <translated>
          <lang which="en">
            <p>Foil One, currently set to "Green"</p>
          </lang>
          <lang which="de">
            <p>Auswahlmöglichkeit 1, derzeit gesetzt auf "Grün"</p>
          </lang>
          <lang which="fr">
            <p>Alternative n°1, valeur de courant: "Vert"</p>
          </lang>
          <lang which="default">
            <p>This is foil One. It is currently set to "Green".</p>
          </lang>
        </translated>
      </foil>
      <foil location="random" value="$colors{red}" name="Foil2">
        <translated>
          <lang which="en">
            <p>Foil Two, currently set to "Red"</p>
          </lang>
          <lang which="de">
            <p>Auswahlmöglichkeit 2, derzeit gesetzt auf "Rot"</p>
          </lang>
          <lang which="fr">
            <p>Alternative n°2, valeur de courant: "Rouge"</p>
          </lang>
          <lang which="default">
            <p>Foil Two, currently set to "Red"</p>
          </lang>
        </translated>
      </foil>
      <foil location="random" value="$colors{blue}" name="Foil3">
        <translated>
          <lang which="en">
            <p>Foil Three, currently set to "Blue"</p>
          </lang>
          <lang which="de">
            <p>Auswahlmöglichkeit 3, derzeit gesetzt auf "Blau"</p>
          </lang>
          <lang which="fr">
            <p>Alternative n°3, valeur de courant: "Bleu"</p>
          </lang>
          <lang which="default">
            <p>Foil Three, currently set to "Blue"</p>
          </lang>
        </translated>
      </foil>
      <foil location="random" value="$colors{red}" name="Foil4">
        <translated>
          <lang which="en">
            <p>Foil Four, currently set to "Red"</p>
          </lang>
          <lang which="de">
            <p>Auswahlmöglichkeit 4, derzeit gesetzt auf "Rot"</p>
          </lang>
          <lang which="fr">
            <p>Alternative n°4, valeur de courant: "Rouge"</p>
          </lang>
          <lang which="default">
            <p>Foil Four, currently set to "Red"</p>
          </lang>
        </translated>
      </foil>
    </foilgroup>
    <hintgroup showoncorrect="no">
    </hintgroup>
  </optionresponse>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/SimpleFormula.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/SimpleFormula.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <script type="loncapa/perl"><![CDATA[
$k=&random(3,6,1);
$formula="a*x^$k";
$m=$k-1;
$derivative="$k*a*x^$m";
]]></script>
  <p>What is the derivative of <tt>$formula</tt> with respect to x?
    <formularesponse samples="a,x at -3,-3:3,3#4" answer="$derivative">
      <responseparam description="Numerical Tolerance" type="tolerance" default="0.00001" name="tol"/>
      <textline size="25"/>
    </formularesponse></p>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/SimpleFormulaCAS.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/SimpleFormulaCAS.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <script type="loncapa/perl"><![CDATA[
$k=&random(3,6,1);
$formula="a*x^$k";
$m=$k-1;
$derivative="$k*a*x^$m";
]]></script>
  <p>What is the derivative of <tt>$formula</tt> with respect to x?
    <formularesponse answer="$derivative">
      <textline size="25"/>
    </formularesponse></p>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/SimpleMatching.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/SimpleMatching.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <p>Match the author to their work.</p>
  <matchresponse max="10" randomize="yes">
    <foilgroup>
      <itemgroup>
        <item name="shakespeare">
          William Shakespeare
        </item>
        <item name="alex">
          Aleksandr Isaevich Solzhenitsyn
        </item>
        <item name="dickinson">
          Emily Dickinson
        </item>
        <item location="bottom" name="none">
          None of the above
        </item>
      </itemgroup>
      <foil location="random" value="none" name="For Whom the Bell Tolls">
        For Whom the Bell Tolls
      </foil>
      <foil location="random" value="alex" name="Gulag">
        The Gulag Archipelago
      </foil>
      <foil location="random" value="none" name="Tom Sawyer">
        Tom Sawyer
      </foil>
      <foil location="random" value="shakespeare" name="King Lear">
        King Lear
      </foil>
      <foil location="random" value="none" name="Gettysburg Address">
        Gettysburg Address
      </foil>
      <foil location="random" value="shakespeare" name="Hamlet">
        Hamlet
      </foil>
    </foilgroup>
  </matchresponse>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/SimpleMathResponse.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/SimpleMathResponse.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <script type="loncapa/perl"><![CDATA[
$x = &random(-1,1,2) * &random(1,5,1);
$y = &random(-1,1,2) * &random(1,3,1);
@args = ($x, $y);
$yaa = $y-3*$x;
if ($yaa > 0) {
  $yaa = "+" . $yaa;
} elsif ($yaa == 0) {
  $yaa = "";
}
$example = "3x$yaa is an example for a function having constant slope which passes through the point ($x | $y). The slope is not given explicitly, so there are different possibilities for this function.";
]]></script>
  <p>State a function y(x) with constant slope which passes through the point ($x | $y).</p>
  <p/>
  <p>y(x) =
    <mathresponse answerdisplay="$example" cas="maxima" args="@args">
      <answer><![CDATA[
y(x):=RESPONSE[1];
hitspoint:is(abs(y(LONCAPALIST[1]) - LONCAPALIST[2]) <= 0.000000001);
islinear:is(diff(y(x),x,2) = 0);
hitspoint and islinear;
]]></answer>
      <textline readonly="no" size="50"/>
    </mathresponse></p>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/SimpleMathResponseR.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/SimpleMathResponseR.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <script type="loncapa/perl"><![CDATA[
$mean=&random(4,9,0.1);
@args=$mean;
$answer="$mean,$mean,$mean would be a simple example";
]]></script>
  <p>Provide a list of 3 numbers (separated by commas) that has a mean value of $mean.
    <mathresponse answerdisplay="$answer" cas="R" args="@args">
      <answer><![CDATA[
x<-c(RESPONSE[1],RESPONSE[2],RESPONSE[3]);
abs(mean(x)-LONCAPALIST[1])<0.001
]]></answer>
      <textline readonly="no" size="50"/>
    </mathresponse></p>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/SimpleRank.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/SimpleRank.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <p>Enter the question text here.</p>
  <rankresponse max="10" randomize="yes">
    <foilgroup>
      <foil location="random" value="1" name="foil1">
        1
      </foil>
      <foil location="random" value="2" name="foil2">
        2
      </foil>
      <foil location="random" value="3" name="foil3">
        3
      </foil>
      <foil location="random" value="2" name="foil4">
        2
      </foil>
      <foil location="random" value="2.0" name="foil5">
        2.0
      </foil>
      <foil location="random" value="4" name="foil6">
        4
      </foil>
      <foil location="random" value="-1" name="foil7">
        -1
      </foil>
    </foilgroup>
  </rankresponse>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/SimpleTrueFalse.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/SimpleTrueFalse.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <p>Type Question Here.
    <optionresponse max="10">
      <foilgroup options="('True','False')" checkboxvalue="True" checkboxoptions="nochoice">
        <foil name="Foil1" value="True">
          This is foil One. It is currently set to "True."
        </foil>
        <foil name="Foil2" value="False">
          This is foil Two. It is currently set to "False."
        </foil>
        <foil name="Foil3" value="True">
          This is foil Three. It is currently set to "True."
        </foil>
        <foil name="Foil4" value="False">
          This is foil Four. It is currently set to "False."
        </foil>
        <foil name="Foil5" value="True">
          This is foil Five. It is currently set to "True."
        </foil>
        <foil name="Foil6" value="False">
          This is foil Six. It is currently set to "False."
        </foil>
      </foilgroup>
      <hintgroup>
      </hintgroup>
    </optionresponse></p>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/StringResponse.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/StringResponse.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <p>Enter your question text here.
    The current answer is "NaCl" and it is case sensitive.
    <stringresponse answer="NaCl" type="cs">
      <textline/>
      <hintgroup>
      </hintgroup>
    </stringresponse></p>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/answerdependent.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/answerdependent.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <parameter name="ordered" type="string_yesno" description="Show Parts One-at-a-Time" default="yes"/>
  <script type="loncapa/perl"><![CDATA[
$lower=&random(3,10,1);
$higher=&random(15,20,1);
$answer=&random($lower,$higher,0.1);
$example='An example would be '.$answer;
# Get the entered answer. First argument is partID, second is responseID
$given=&submission(1,11);
if ($given=~/\d/) { $answer=$given; $example='You chose '.$given; }
$add=&random(3,6,0.1);
$result=$given+$add;
]]></script>
  <part id="1">
    <p>Give a number between $lower and $higher.
      <customresponse answerdisplay="$example" id="11">
        <answer type="loncapa/perl"><![CDATA[
# We do not want a vector
if ($submission=~/\,/) { return 'EXTRA_ANSWER'; }
# No units needed
if ($submission=~/^\d+\s+\w+$/) { return 'UNIT_NOTNEEDED'; }
# Need a numerical answer here
if ($submission!~/^[\d\.]+$/) { return 'WANTED_NUMERIC'; }
# is it correct?
if (($submission>=$lower) && ($submission<=$higher)) { return 'EXACT_ANS'; }
return 'INCORRECT';
]]></answer>
        <textline readonly="no"/>
      </customresponse></p>
  </part>
  <part id="2">
    <p>What do you get if you add $add to your number?
      <numericalresponse answer="$result">
        <responseparam type="tolerance" default="1%" name="tol" description="Numerical Tolerance"/>
        <textline readonly="no"/>
      </numericalresponse></p>
  </part>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/blank.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/blank.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/custom_equation.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/custom_equation.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <script type="loncapa/perl"><![CDATA[
$a=&random(3,8,1);
$b=$a+&random(2,5,1);
$sample="x=$b or y=$a*x would be correct";

sub compareequations {
    my ($var,$value,$equation,$real,$lenient)=@_;
# var is the variable given
# value is the expected value of $var, $var=$value
# equation is the equation to be tested
# real: if set, operate only in the real realm
# lenient: if set, give credit even if the correct answer is only one of the possible answers, e.g., x^2=9 is "correct" if x=3 is expected
    $correctanswer=&cas('maxima','trigsimp(trigreduce('.$var.'='.$value.'))');
    if ($correctanswer=~/^Error\:/) { return $correctanswer; }
    $solution=&cas('maxima','trigsimp(trigreduce(solve('.$equation.','.$var.')))');
    if ($solution=~/^Error\:/) { return $solution; }
    $solution=~s/^\[//;
    $solution=~s/\]$//;
    $total=0;
    $found=0;
    foreach my $thissolution (split(/\s*\,\s*/,$solution)) {
         if (($real) && ($thissolution=~/\%i/)) { next; }
         $total++;
         if ($thissolution eq $correctanswer) { $found=1; }
    }
    if ((!$lenient) && ($total>1)) { return 'false'; }
    if ($found) { return 'true'; }
    return 'false';
}
]]></script>
  <p>Given an equation for an asymptote of</p>
  <m eval="on">$$
    y=$a\cdot x+\frac{1}{x-$b}
  $$</m>
  <customresponse answerdisplay="$sample">
    <answer type="loncapa/perl"><![CDATA[
$first=&compareequations('y',"$a*x",$submission,1);
if ($first=~/^Error/) { return 'BAD_FORMULA'; }
if ($first=~/true/) { return 'EXACT_ANS'; }

$second=&compareequations('x',$b,$submission,1);
if ($second=~/^Error/) { return 'BAD_FORMULA'; }
if ($second=~/true/) { return 'EXACT_ANS'; }

return 'INCORRECT';
]]></answer>
    <textline readonly="no"/>
  </customresponse>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/customhints.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/customhints.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <script type="loncapa/perl"><![CDATA[
# Construct an Egyptian Fraction that can be represented by three terms with denominators between 3 and 12
@denominators=(&random_permutation(&random(1,1000,1),(3..12)))[0..2];
$egyptian='1/'.(join('+1/',sort{$a<=>$b}(@denominators)));
$possible="A possible solution is $egyptian";

# Let the CAS figure out the value
$solution=&cas('maxima',$egyptian);

# Subroutine that checks if the provided term is indeed an Egyptian Fraction
sub analyze {
    my ($expression)=@_;
    $expression=~s/\s//gs;
    $expression=~s/\+?1\//\,/gs;
     if ($expression=~/^(\,[0-9]+)+$/) {
# Format is indeed 1/n+1/m+...
        $last=-1;
        foreach $number (sort { $a<=>$b } split(/\,/,$expression)) {
# Is a number used twice?
             if ($last==$number) { return(0,1); }
             $last=$number;
          }
          return(0,0);
     }
     return(1,0);
}
]]></script>
  <p>Write $solution as an Egyptian Fraction</p>
  <customresponse answerdisplay="$possible" id="11">
    <answer type="loncapa/perl"><![CDATA[
# Analyze the format
($formaterror,$doubleerror)=&analyze($submission);
if ($formaterror || $doubleerror) { return 'WRONG_FORMAT'; }
# It is an Egyptian Fraction, is the value correct?
if (&cas('maxima',$submission.'-('.$egyptian.')') eq '0') {
    return 'EXACT_ANS';
}
return 'INCORRECT';
]]></answer>
    <textline readonly="no" size="40"/>
    <customhint name="format" id="12">
      <answer type="loncapa/perl"><![CDATA[
if ((&analyze($submission))[0]) { return 'EXACT_ANS'; }
return 'INCORRECT';
]]></answer>
    </customhint>
    <hintpart on="format">
      <p>Egyptian Fractions have a format of 1/number+1/number+..., for example 1/3+1/17+1/52.</p>
    </hintpart>
    <customhint name="double" id="13">
      <answer type="loncapa/perl"><![CDATA[
if ((&analyze($submission))[1]) { return 'EXACT_ANS'; }
return 'INCORRECT';
]]></answer>
    </customhint>
    <hintpart on="double">
      <p>Egyptian Fractions cannot have the same denominator more than once. For example, 1/3+1/17+1/4+1/17 is not an Egyptian Fraction, as it has 17 twice as denominator.</p>
    </hintpart>
  </customresponse>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/custompartial.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/custompartial.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <parameter name="lenient" type="string_yesno" description="Partially correct is answerable" default="yes"/>
  <p>Complete the sentence:
    <customresponse id="11">
      <notsolved>
        <p>The q<textline readonly="no" size="5"/> brown fox jum<textline readonly="no" size="3"/> over the <textline readonly="no" size="3"/>zy d<textline readonly="no" size="3"/>.</p>
      </notsolved>
      <solved>
        <p>The q<b>uick</b> brown fox jum<b>ps</b> over the <b>la</b>zy d<b>og</b>.</p>
      </solved>
      <answer type="loncapa/perl"><![CDATA[
@answer=('uick','ps','la','og');
$correct=0;
for ($i=0;$i<=$#$submission;$i++) {
    $$submission[$i]=~s/\s//gs;
    if ($$submission[$i] eq $answer[$i]) { $correct++; }
}
if ($correct==$#answer+1) { return 'EXACT_ANS'; }
if ($correct==0) { return 'INCORRECT'; }
return('ASSIGNED_SCORE',$correct/($#answer+1));
]]></answer>
    </customresponse></p>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/customunit.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/customunit.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <script type="loncapa/perl"><![CDATA[
$pecks=&random(3,5,1);
$bushels=&random(2,6,1);
$total=4.4*(8*$bushels+2*$pecks);
]]></script>
  <p>You go to the market and buy $pecks peck of apples and $bushels bushel of potatoes. How much produce did you buy? Note that these are dry goods.
    <numericalresponse answer="$total" unit="L" format="3s">
      <responseparam type="tolerance" default="5%" name="tol" description="Numerical Tolerance"/>
      <responseparam name="customunits" type="string_any" description="Custom-defined Units" default="peck=2*gallon,bushel=8*gallon,gallon=4.4*L"/>
      <textline readonly="no"/>
    </numericalresponse></p>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/dynamicgraph.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/dynamicgraph.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <parameter name="maxtries" id="12" type="int_pos" default="99" description="Maximum Number of Tries"/>
  <script type="loncapa/perl"><![CDATA[
$amplitude=&random(2.5,3.8,0.1);
$omega=&random(1,3,0.1);
$k=&random(0.2,0.4,0.1);
$phi=&random(0.3,3,0.1);
# Correct function from computer
$function="$amplitude*exp(-$k*x)*sin($omega*x+$phi)";
# Get user function with &submission(partID,responseID),
# use implicit multiplication to insert "*" where obvious
$userfunction=&implicit_multiplication(&submission(0,11));
if ($userfunction) {
    if (&cas('maxima',$userfunction)=~/^(error|incorrect)/i) {
# Don't plot formulas with syntax errors, they would
# result in a broken plot.
        $userfunction='0';
    }
    $remark="The function you entered is indicated in red.";
} else {
   $userfunction='0';
   $remark='';
}
# The user function will be evaluated fuzzy (with tolerance),
# since the user is unlikely to hit exactly the right function.
]]></script>
  <gnuplot width="600" grid="on" align="left" font="9" height="400" border="on" samples="100" bgcolor="xffffff" fgcolor="x000000" transparent="off" plottype="Cartesian">
    <axis xmin="0" ymax="4" color="x000000" ymin="-4" xmax="10"/>
    <xlabel>x</xlabel>
    <ylabel>f(x)</ylabel>
    <curve linestyle="linespoints" name="ProblemPlot" color="x000000" pointtype="0">
      <function>$function</function>
    </curve>
    <curve linestyle="lines" name="UserPlot" color="xFF0000" pointtype="0">
      <function>$userfunction</function>
    </curve>
  </gnuplot>
  <p/>
  <p>Match the function indicated in black. $remark</p>
  <p>f(x)=
    <formularesponse answer="$function" samples="x at 0;1;2;3;4;5;6;7;8;9" id="11">
      <textline size="60" readonly="no"/>
      <responseparam name="tol" default="0.4" description="Numerical Tolerance" type="tolerance"/>
    </formularesponse></p>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/examupload.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/examupload.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <displaytitle/>
  <parameter name="scoreformat" description="Format for display of score" type="string"/>
  <parameter name="handgrade" description="Hand Grade" default="yes" type="string_yesno"/>
  <script type="loncapa/perl"><![CDATA[
$weight=&parameter_setting('weight');
if ((!defined($weight)) || ($weight eq '')) { $weight=1; }
$awarded=&stored_data('awarded');
if (!defined($awarded)) { $awarded=0; }
$scoreformat=&parameter_setting('scoreformat');
if (!defined($scoreformat) || $scoreformat eq '') { $scoreformat="2f"; }
$display='';
if (&parameter_setting('problemstatus')!~/^no/) {
   if (!defined($awarded)) {
      $display=$weight.' possible points.';
   } else {
      $display='You have '.&format($awarded*$weight,$scoreformat).' out of '.
            $weight.' possible points.';
   }
}
$comment=&stored_data('comment');
if (!defined($comment) || $comment!~/\w/) {
   $comment='';
} else {
   $comment='<br /><table><tr><td bgcolor="#FFFFDD">'.$comment.'</td></tr></table>';
}
$gradeinfo=&stored_data('gradeinfo');
if (!defined($gradeinfo) || $gradeinfo!~/\w/) {
   $gradeinfo='';
} else {
   $gradeinfo='<br /><table><tr><td bgcolor="#DDDDFF"><font size="+2">'.$gradeinfo.'</font></td></tr></table>';
}
]]></script>
  <p>$display</p>
  <instructorcomment>
    <p/>
    <p><span style="color:red">You can modify the rounding of the score display using the "scoreformat" setting.
You can upload comments and grade information with this resource when uploading CSV files. Feedback on grades can be controlled using the problemstatus parameter.</span></p>
  </instructorcomment>
  <p>$gradeinfo
    $comment</p>
  <block condition="0">
    <numericalresponse id="score"/>
  </block>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/extreme.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/extreme.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <parameter name="ordered" type="string_yesno" description="Show Parts One-at-a-Time" default="yes"/>
  <script type="loncapa/perl"><![CDATA[
$location=&random(4,10,1);
$example="(x-$location)^2";
if (&random(0,1,1)) {
   $text='minimum';
   $factor=1;
} else {
   $text='maximum';
   $factor=-1;
   $example='-'.$example;
}

# Get user input, run through function that adds "*" to 3x, etc
$userfunction=&implicit_multiplication(&submission(1,11));
if ($userfunction) {
# Determine value at extreme point
   $extremevalue=&cas('maxima',"float(at($userfunction,x=$location))");
   if ($extremevalue=~/^(error|incorrect)/i) {
# cas could not evaluate the function
      $userfunction='0';
      $extremevalue=0;
      $remark='Your function could not be evaluated.';
   } else {
      $remark='Your function is plotted.';
      $example=$userfunction;
   }
} else {
   $userfunction='0';
   $extremevalue=0;
   $remark='';
}
# Plot boundaries
$minx=$location-5;
$maxx=$location+5;
$miny=$extremevalue-5;
$maxy=$extremevalue+5;
@args=($location,$factor);
]]></script>
  <part id="1">
    <gnuplot width="400" solid="0" plotcolor="monochrome" gridlayer="off" bmargin="default" font="9" alttag="Plot" bgcolor="xffffff" texfont="22" transparent="off" plottype="Cartesian" rmargin="default" gridtype="Cartesian" minor_ticscale="0.5" fontface="sans-serif" grid="on" align="middle" texwidth="93" height="300" border="on" samples="100" fgcolor="x000000" major_ticscale="1" tmargin="default" lmargin="default" fillstyle="empty">
      <curve linestyle="lines" color="x000000" pointtype="1" linetype="solid" linewidth="1" name="" limit="closed" pointsize="1">
        <function>$userfunction</function>
      </curve>
      <axis yzero="off" xformat="on" xmin="$minx" yformat="on" color="x000000" xmax="$maxx" xzero="off" ymax="$maxy" ymin="$miny"/>
    </gnuplot>
    <p>$remark</p>
    <p>Give a differentiable real function <m>$f(x)$</m> that has a $text at <m eval="on">$x=$location$</m>.</p>
    <p><m>$f(x)=$</m>
      <mathresponse answerdisplay="$example" cas="maxima" args="@args" id="11">
        <answer><![CDATA[
y(x):=RESPONSE[1];
hasextreme:is(at(diff(y(x),x,1),x=LONCAPALIST[1])=0);
isrightkind:false;
k:0;
s:0;
if hasextreme then for i:2 while s=0 and not is(diff(y(x),x,i)=0) do (s:at(diff(y(x),x,i),x=LONCAPALIST[1]),k:i);
if evenp(k) and is(LONCAPALIST[2]*s>0) then isrightkind:true;
hasextreme and isrightkind;
]]></answer>
        <textline readonly="no" size="50"/>
      </mathresponse></p>
  </part>
  <part id="2">
    <script type="loncapa/perl"><![CDATA[
$previous=&maxima_cas_formula_fix($example);
$value=&cas('maxima',"float(at($previous,x=$location))");
]]></script>
    <p>What is the value of your function at this $text?
      <numericalresponse answer="$value">
        <responseparam type="tolerance" default="5%" name="tol" description="Numerical Tolerance"/>
        <textline readonly="no"/>
      </numericalresponse></p>
  </part>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/functionplotback.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/functionplotback.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <script type="loncapa/perl"><![CDATA[
$x=&random(-5,5,2);
$y=&random(-3,3,2);
$a=&random(0.5,0.9,0.1);
if (&random(0,1,1)) {
   $function="$a*(x-($x))^2+($y)";
   $answer="2*$a*(x-($x))";
   $relation='gt';
} else {
   $function="-$a*(x-($x))^2+($y)";
   $answer="-2*$a*(x-($x))";
   $relation='lt';
}
]]></script>
  <p>Provide a sketch of the derivative of the indicated function</p>
  <functionplotresponse xaxisvisible="yes" xlabel="x" gridvisible="yes" ylabel="y(x)" id="11" xmax="10" yaxisvisible="yes" xmin="-10" ymin="-10" ymax="10" answerdisplay="$answer">
    <functionplotelements>
      <spline initx="-4" inity="0" index="A" order="3" scalex="8"/>
      <backgroundplot function="$function" fixed="yes"/>
    </functionplotelements>
    <functionplotruleset>
      <functionplotrule relationship="eq" xinitial="$x" value="0" derivativeorder="0" percenterror="2" index="extreme"/>
      <functionplotrule relationship="$relation" xinitiallabel="start" xfinallabel="end" value="0" derivativeorder="1" percenterror="10" index="slope"/>
      <functionplotrule relationship="eq" xinitiallabel="start" xfinallabel="end" value="0" derivativeorder="2" percenterror="50" index="linear"/>
    </functionplotruleset>
  </functionplotresponse>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/functionplotone.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/functionplotone.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <script type="loncapa/perl"><![CDATA[
$maxacc=&random(3,5,1);
$maxbraketime=&random(6,10,2);
$wait=&random(2,4,1);
]]></script>
  <p>At t=0 s, a car cruises at a constant positive velocity. Suddenly, a light switches to red. At t=$maxbraketime s, the driver is maximum on the brake. The car then stops in front of the red light for over $wait seconds. Eventually, it drives off, and then again cruises at a constant velocity. The car cannot accelerate with more than $maxacc m/s<sup>2</sup>.</p>
  <p>Provide a graph of its acceleration as a function of time.</p>
  <functionplotresponse xaxisvisible="yes" xlabel="t [s]" gridvisible="yes" ylabel="a(t) [m/s^2]" id="11" xmax="30" yaxisvisible="yes" xmin="-4" ymin="-8" ymax="8" answerdisplay="0::$maxbraketime-4,-7*(exp(-0.5*(x-$maxbraketime)^2)-exp(-8)):$maxbraketime-4:$maxbraketime+4,0:$maxbraketime+4:$maxbraketime+5+$wait,($maxacc-0.3)*(exp(-(x-($maxbraketime+7+$wait))^2)-exp(-4)):$maxbraketime+5+$wait:$maxbraketime+9+$wait,0:$maxbraketime+9+$wait">
    <functionplotelements>
      <spline initx="-2" inity="0" index="A" order="8" scalex="28"/>
    </functionplotelements>
    <functionplotruleset>
      <functionplotrule relationship="eq" xinitiallabel="start" xfinallabel="brake" value="0" derivativeorder="0" percenterror="1" minimumlength="1"/>
      <functionplotrule relationship="eq" xinitial="0" value="0" derivativeorder="0" percenterror="1"/>
      <functionplotrule relationship="lt" xinitiallabel="brake" xfinallabel="stop" value="-0.1" derivativeorder="0"/>
      <functionplotrule relationship="eq" xinitial="$maxbraketime" value="0" derivativeorder="1" percenterror="40"/>
      <functionplotrule relationship="eq" xinitiallabel="stop" xfinallabel="driveoff" value="0" derivativeorder="0" percenterror="1" minimumlength="$wait"/>
      <functionplotrule relationship="gt" xinitiallabel="driveoff" xfinallabel="cruise" value="0.1" derivativeorder="0"/>
      <functionplotrule relationship="le" xinitiallabel="driveoff" xfinallabel="cruise" value="$maxacc" derivativeorder="0"/>
      <functionplotrule relationship="eq" xinitiallabel="cruise" xfinallabel="end" value="0" derivativeorder="0" percenterror="1" minimumlength="1"/>
    </functionplotruleset>
  </functionplotresponse>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/functionplottwo.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/functionplottwo.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <script type="loncapa/perl"><![CDATA[
$x=&random(5,10,1);
]]></script>
  <p>Make a sketch of the electrostatic potential (zero at infinity) of a negative point charge located at x=$x m.</p>
  <functionplotresponse xaxisvisible="yes" xlabel="x [m]" gridvisible="yes" ylabel="V(x) [V]" id="11" xmax="30" yaxisvisible="yes" xmin="-4" ymin="-10" ymax="10" answerdisplay="-4/abs(x-$x)">
    <functionplotelements>
      <spline initx="-2" inity="0" index="A" order="2" scalex="4"/>
      <spline index="B" order="2" initx="12" inity="0" scalex="4"/>
    </functionplotelements>
    <functionplotruleset>
      <functionplotrule relationship="eq" value="undef" derivative="0" xinitial="$x" index="chargeposition" derivativeorder="0"/>
      <functionplotrule relationship="lt" value="0" derivativeorder="0" xinitiallabel="start" xfinallabel="chargeleft" index="negleft"/>
      <functionplotrule relationship="lt" value="0" derivativeorder="1" xinitiallabel="start" xfinallabel="chargeleft"/>
      <functionplotrule relationship="le" value="0" derivativeorder="2" xinitiallabel="start" xfinallabel="chargeleft" percentageerror="20"/>
      <functionplotrule relationship="eq" xinitiallabel="chargeleft" xfinallabel="chargeright" value="undef" derivativeorder="0"/>
      <functionplotrule relationship="lt" value="0" derivativeorder="0" xinitiallabel="chargeright" xfinallabel="end" index="negright"/>
      <functionplotrule relationship="gt" value="0" derivativeorder="1" xinitiallabel="chargeright" xfinallabel="end"/>
      <functionplotrule relationship="le" value="0" derivativeorder="2" xinitiallabel="chargeright" xfinallabel="end" percentageerror="20"/>
    </functionplotruleset>
    <hintgroup showoncorrect="no">
      <hintpart on="chargeposition">
        The charge is not in the correct position.
      </hintpart>
      <hintpart on="negleft">
        The potential is zero at negative infinity, and the charge is negative.
      </hintpart>
      <hintpart on="negright">
        The potential is zero at positive infinity, and the charge is negative.
      </hintpart>
    </hintgroup>
  </functionplotresponse>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/functionplotvector.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/functionplotvector.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <script type="loncapa/perl"><![CDATA[
$slope=&random(0.5,0.8,0.1);
$function="2+$slope*x";
$massx=&random(3.5,6.5,0.1);
$massy=2.1+$slope*$massx;
$angle=180.*atan($slope)/$pi;
$perp=$angle+90.;
]]></script>
  <p>A mass is sliding down the indicated incline with a constant velocity. Construct the free body diagram, making sure the forces are approximately proportionally correct. Leave unused vectors unattached.</p>
  <p><i>You can use the red-colored vector sum tool to help you construct your diagram; check the boxes for the vectors you want included in the sum. The tool is just there to help you, its settings are not graded.</i></p>
  <functionplotresponse xaxisvisible="no" xlabel="x" gridvisible="yes" ylabel="y" id="11" xmax="10" yaxisvisible="no" xmin="0" ymin="0" ymax="10" width="450" height="450">
    <functionplotelements>
      <backgroundplot function="$function" fixed="yes"/>
      <plotobject label="Mass" x="$massx" y="$massy"/>
      <plotvector label="Gravity" tailx="7" taily="1" tipx="8" tipy="1"/>
      <plotvector label="KineticFriction" tailx="7" taily="2" tipx="8" tipy="2"/>
      <plotvector label="StaticFriction" tailx="7" taily="3" tipx="8" tipy="3"/>
      <plotvector label="NormalForce" tailx="7" taily="4" tipx="8" tipy="4"/>
      <drawvectorsum label="Sum" tailx="$massx" taily="$massy" showvalue="no" vectorlist="Gravity,KineticFriction,StaticFriction,NormalForce"/>
    </functionplotelements>
    <functionplotruleset>
      <functionplotvectorrule index="Grav" vector="Gravity" attachpoint="Mass" angle="270" angleerror="2"/>
      <functionplotvectorrule index="Norm" vector="NormalForce" attachpoint="Mass" angle="$perp" angleerror="4"/>
      <functionplotvectorrule index="KFriction" vector="KineticFriction" attachpoint="Mass" angle="$angle" angleerror="3"/>
      <functionplotvectorrule index="SFriction" vector="StaticFriction" notattachpoint="Mass"/>
      <functionplotvectorsumrule vectors="Gravity,NormalForce,KineticFriction" length="0" lengtherror="2" index="Total"/>
    </functionplotruleset>
    <hintgroup showoncorrect="no">
      <hintpart on="Grav">
        Gravity acts on the mass and points straight down.
      </hintpart>
      <hintpart on="SFriction">
        The mass is sliding.
      </hintpart>
      <hintpart on="Total">
        What should be the net force on the mass?
      </hintpart>
      <hintpart on="Norm">
        What should be the direction of the normal force?
      </hintpart>
    </hintgroup>
  </functionplotresponse>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/numMultiAnswerUnordered.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/numMultiAnswerUnordered.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <script type="loncapa/perl"><![CDATA[
$a = &random(1,7,1);
$b = $a*$a;
]]></script>
  <p>In <m eval="on">$ x^2 - $b = 0 $</m>, what are possible values of <m>$ x $</m>?</p>
  <numericalresponse>
    <answergroup type="ordered">
      <answer type="unordered">
<value>-$a</value>
            <value>$a</value>
        </answer>
    </answergroup>
    <textline size="10" readonly="no"/>
    <textline size="10" readonly="no"/>
  </numericalresponse>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/numPrePro.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/numPrePro.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <script type="loncapa/perl"><![CDATA[
#Enter the computations here
$idx=&random(0,2,1);
$season=('nice spring','hot summer','cool fall')[$idx];
$temp=273+(20,30,10)[$idx];

sub abstemp {
    my ($answer,$unit)=@_;
    if ($unit=~/degC/) { $answer+=273; }
    if ($unit=~/degF/) { $answer+=460; }
    return $answer;
}
]]></script>
  <p>What is the temperature in Germany on a $season day?
    <numericalresponse answer="$temp" id="11" preprocess="abstemp" unit="K">
      <responseparam name="tol" type="tolerance" default="5" description="Numerical Tolerance"/>
      <textline readonly="no"/>
    </numericalresponse></p>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/numerical.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/numerical.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <script type="loncapa/perl"><![CDATA[
#Enter the computations here
$a=&random(1,10,1);
$b=&random(1,10,1);
$c=$a+$b;
]]></script>
  <p>Enter the problem text here.
    What is $a + $b?
    <numericalresponse answer="$c">
      <responseparam type="tolerance" default="5%" name="tol" description="Numerical Tolerance"/>
      <textline/>
    </numericalresponse></p>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/organic.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/organic.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <p>The image below is acetic acid <chem>C2H4O2</chem></p>
  <organicstructure width="250" options="border" texwidth="70" molecule="4 3 C 6.33 -6.22 C 7.73 -6.22 O 8.43 -7.43 O 8.43 -5.00 1 2 1 2 3 1 2 4 2"/>
  <p>Complete acetic acid.</p>
  <organicresponse jmeanswer="4 3 C 6.33 -6.22 C 7.73 -6.22 O 8.43 -7.43 O 8.43 -5.00 1 2 1 2 3 1 2 4 2" answer="CC(=O)O" molecule="3 2 C 11.42 -7.84 C 12.64 -8.54 O 12.64 -9.94 1 2 1 2 3 2">
    <textline readonly="yes"/>
  </organicresponse>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/organic_hint.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/organic_hint.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <p>Draw ethane.</p>
  <organicresponse answer="CC" jmeanswer="2 1 C 11.14 -7.90 C 12.36 -8.60 1 2 1">
    <textline readonly="yes"/>
    <hintgroup showoncorrect="no">
      <organichint name="propane" answer="CCC"/>
      <hintpart on="propane">
        That's propane.
      </hintpart>
    </hintgroup>
  </organicresponse>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/randomvalueradio.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/randomvalueradio.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <parameter name="maxtries" id="11" type="int_pos" default="2" description="Maximum Number of Tries"/>
  <script type="loncapa/perl"><![CDATA[
$idx=&random(0,3,1);
$city=('Berlin','New York','Sao Paulo','Canberra')[$idx];
$southern=('false','false','true','true')[$idx];
$northern=('true','true','false','false')[$idx];
$europe=('true','false','false','false')[$idx];
$australia=('false','false','false','true')[$idx];
if ($idx==2) { $anothercontinent='Asia'; } else { $anothercontinent='South America'; }
]]></script>
  <p>Which one of the following statements is true for $city?</p>
  <radiobuttonresponse direction="vertical" max="3" id="12" randomize="yes">
    <foilgroup>
      <foil location="random" value="$europe" name="foil1">
        In Europe
      </foil>
      <foil location="random" value="$australia" name="foil2">
        In Australia
      </foil>
      <foil location="random" value="false" name="foil3">
        In $anothercontinent
      </foil>
      <foil location="random" value="$southern" name="foil4">
        In the Southern Hemisphere
      </foil>
      <foil location="random" value="$northern" name="foil5">
        In the Northern Hemisphere
      </foil>
      <foil location="bottom" value="true" name="foil6">
        None of the above
      </foil>
    </foilgroup>
  </radiobuttonresponse>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/reaction.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/reaction.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <script type="loncapa/perl"><![CDATA[
# you could randomize here
$formula = "C2H5OH";
$name = "ethanol";
$answer = "C2H5OH + 3O2 -> 2CO2 + 3H2O";
]]></script>
  <p>Write a balanced reaction for the complete combustion of $name (<chem>$formula</chem>) in oxygen, using the smallest integer coefficients.</p>
  <reactionresponse answer="$answer" initial="$formula ->">
    <textline readonly="yes"/>
  </reactionresponse>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/reaction_hint.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/reaction_hint.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <p>Write a balanced reaction for hydrogen gas and oxygen gas forming water, using the smallest integer coefficients.</p>
  <reactionresponse answer="2H2 + O2 -> 2H2O" initial="">
    <textline readonly="yes"/>
    <hintgroup showoncorrect="no">
      <reactionhint answer="H2 + O -> H2O" name="gas"/>
      <hintpart on="gas">
        Oxygen is a diatomic gas.
      </hintpart>
      <stringhint answer="/\((s|l|g)\)/" name="state" type="re"/>
      <hintpart on="state">
        Do not include symbols for state of matter in your answer.
      </hintpart>
    </hintgroup>
  </reactionresponse>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/sampleexternal.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/sampleexternal.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <parameter name="externalurl" type="string" description="URL for external testing"/>
  <script type="loncapa/perl"><![CDATA[
sub fact {
   my $n=shift;
   if ($n==1) {
      return $n;
   } else {
      return $n*&fact($n-1);
   }
}

$testvalues='0=1';
foreach $i (1..3) {
   $rand=&random(3,8,1);
   $testvalues.=','.$rand.'='.&fact($rand);
}

#
# Set anything else you want to pass in this hash.
# Name-value pairs will be passed as posted form parameters.
#
%args=('somecode' => (<<'ENDCODE')
sub negative {
   my ($number)=@_;
   return $number<0;
}
ENDCODE
);
# Get a URL if it is externally set
$externalurl=&parameter_setting('externalurl');
#
# Important: set your default URL here
#
unless ($externalurl=~/\w/) { $externalurl='http://localhost/cgi-bin/sampleexternal.pl'; }
#
]]></script>
  <p>Write a Perl subroutine called <tt>factorial</tt>, which returns the factorial of its argument, e.g. <tt>&factorial(17)=17!</tt>. You can use the
    boolean function <tt>&negative(number)</tt>, which returns <tt>true</tt> if the argument is negative.</p>
  <instructorcomment>
    <p>Sample code for an evaluation script can be found <a href="/res/adm/includes/templates/sampleexternal.pl">here.</a></p>
  </instructorcomment>
  <externalresponse url="$externalurl" answer="$testvalues" form="%args" answerdisplay="The most elegant (and dangerous) solution is recursive.">
    <textfield/>
  </externalresponse>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/stringPrePro.problem.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems/stringPrePro.problem.xml
<?xml version="1.0" encoding="UTF-8"?>
<problem>
  <script type="loncapa/perl"><![CDATA[
$idx=&random(0,2,1);
$word=('Maus','Horn','Huhn')[$idx];
$plural=('Mäuse','Hörner','Hühner')[$idx];

sub umlaute {
    my $answer=shift;
    $answer=~s/ae/ä/g;
    $answer=~s/oe/ö/g;
    $answer=~s/ue/ü/g;
    return $answer;
}
]]></script>
  <p>What is the plural of German "$word"?
    <stringresponse answer="$plural" type="cs" preprocess="umlaute">
      <textline readonly="no" addchars="Ä,Ö,Ü,ä,ö,ü,ß"/>
    </stringresponse></p>
</problem>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/responses/customresponse.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/responses/customresponse.xml
<?xml version="1.0" encoding="UTF-8"?>
<customresponse>
  <answer>
# We do not want a vector
if ($submission=~/\,/) { return 'EXTRA_ANSWER'; }
# No units needed
if ($submission=~/^\d+\s+\w+$/) { return 'UNIT_NOTNEEDED'; }
# Need a numerical answer here
if ($submission!~/^[\d\.\-]+$/) { return 'WANTED_NUMERIC'; }
$difference=abs(42-$submission);
if ($difference==0) { return 'EXACT_ANS'; }
if ($difference < 0.001) { return 'APPROX_ANS'; }
return 'INCORRECT';
  </answer>
  <textline/>
</customresponse>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/responses/essayresponse.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/responses/essayresponse.xml
<?xml version="1.0" encoding="UTF-8"?>
<essayresponse>
  <textfield>
  </textfield>
</essayresponse>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/responses/externalresponse.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/responses/externalresponse.xml
<?xml version="1.0" encoding="UTF-8"?>
<externalresponse url="$url" answer="$answer" form="%args">
  <textfield>
  </textfield>
</externalresponse>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/responses/formularesponse.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/responses/formularesponse.xml
<?xml version="1.0" encoding="UTF-8"?>
<formularesponse answer="x">
  <responseparam name="tol" type="tolerance" description="Numerical Tolerance" default="0.00001"/>
  <textline size="25"/>
</formularesponse>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/responses/functionplotresponse.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/responses/functionplotresponse.xml
<?xml version="1.0" encoding="UTF-8"?>
<functionplotresponse>
  <functionplotelements>
  </functionplotelements>
  <functionplotruleset>
  </functionplotruleset>
</functionplotresponse>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/responses/imageresponse.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/responses/imageresponse.xml
<?xml version="1.0" encoding="UTF-8"?>
<imageresponse>
  <foilgroup>
    <foil name="foil1">
      <image>man1.jpg</image>
      <text>Click on an eye</text>
      <rectangle>(127,139)-(186,178)</rectangle>
      <rectangle>(242,139)-(306,183)</rectangle>
    </foil>
    <foil name="foil2">
      <image>man1.jpg</image>
      <text>Click on the nose</text>
      <rectangle>(192,180)-(227,222)</rectangle>
    </foil>
    <foil name="foil3">
      <image>man1.jpg</image>
      <text>Click on the mouth</text>
      <rectangle>(142,226)-(294,293)</rectangle>
    </foil>
  </foilgroup>
</imageresponse>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/responses/matchresponse.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/responses/matchresponse.xml
<?xml version="1.0" encoding="UTF-8"?>
<matchresponse>
  <foilgroup>
    <itemgroup>
      <item name="item1">
        item1
      </item>
      <item name="item2">
        item2
      </item>
    </itemgroup>
    <foil name="foil1" value="item1">
      foil1 matching item1
    </foil>
    <foil name="foil2" value="item2">
      foil2 matching item2
    </foil>
  </foilgroup>
</matchresponse>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/responses/mathresponse.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/responses/mathresponse.xml
<?xml version="1.0" encoding="UTF-8"?>

<mathresponse cas="maxima">
  <answer>
y(x):=RESPONSE[1];

  </answer>
  <textline size="40"/>
</mathresponse>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/responses/numericalresponse.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/responses/numericalresponse.xml
<?xml version="1.0" encoding="UTF-8"?>
<numericalresponse answer="42">
  <responseparam name="tol" type="tolerance" description="Numerical Tolerance" default="5%"/>
  <textline/>
</numericalresponse>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/responses/optionresponse.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/responses/optionresponse.xml
<?xml version="1.0" encoding="UTF-8"?>
<optionresponse>
  <foilgroup options="('green','red','blue')">
    <foil name="foil1" value="red">
      foil1: red
    </foil>
    <foil name="foil2" value="green">
      foil2: green
    </foil>
    <foil name="foil3" value="blue">
      foil3: blue
    </foil>
    <foil name="foil4" value="red">
      foil4: red
    </foil>
  </foilgroup>
</optionresponse>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/responses/organicresponse.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/responses/organicresponse.xml
<?xml version="1.0" encoding="UTF-8"?>
<organicresponse>
  <textline readonly="yes"/>
</organicresponse>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/responses/radiobuttonresponse.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/responses/radiobuttonresponse.xml
<?xml version="1.0" encoding="UTF-8"?>
<radiobuttonresponse>
  <foilgroup>
    <foil name="foil1" value="true">
      correct option
    </foil>
    <foil name="foil2" value="false">
      wrong option 1
    </foil>
    <foil name="foil3" value="false">
      wrong option 2
    </foil>
  </foilgroup>
</radiobuttonresponse>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/responses/rankresponse.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/responses/rankresponse.xml
<?xml version="1.0" encoding="UTF-8"?>
<rankresponse>
  <foilgroup>
    <foil name="foil1" value="1">
      1
    </foil>
    <foil name="foil2" value="2">
      2
    </foil>
  </foilgroup>
</rankresponse>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/responses/reactionresponse.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/responses/reactionresponse.xml
<?xml version="1.0" encoding="UTF-8"?>
<reactionresponse answer="$answer" initial="$formula ->">
  <textline readonly="yes"/>
</reactionresponse>

Index: modules/damieng/graphical_editor/loncapa_daxe/web/templates/responses/stringresponse.xml
+++ modules/damieng/graphical_editor/loncapa_daxe/web/templates/responses/stringresponse.xml
<?xml version="1.0" encoding="UTF-8"?>
<stringresponse answer="expected_answer">
  <textline/>
</stringresponse>


More information about the LON-CAPA-cvs mailing list