[prev in list] [next in list] [prev in thread] [next in thread] 

List:       lon-capa-cvs
Subject:    [LON-CAPA-cvs] cvs: modules /damieng/clean_xml clean_xml.pl html_to_xml.pm loncapa.xsd post_xml.pm p
From:       damieng <damieng () source ! lon-capa ! org>
Date:       2015-04-17 15:35:10
Message-ID: cvsdamieng1429284910 () cvsserver
[Download RAW message or body]

This is a MIME encoded message


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
  


["damieng-20150417153510.txt" (text/plain)]

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','ta \
ble','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@hostname>
    $result .= "&lt;$tagname&gt;";
    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','standalon \
e','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/&/&amp;/g;
      $att_value =~ s/</&lt;/g;
      $att_value =~ s/>/&gt;/g;
      $att_value =~ s/"/&quot;/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/&/&amp;/g;
  $dtext =~ s/</&lt;/g;
  $dtext =~ s/>/&gt;/g;
  $dtext =~ s/"/&quot;/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*-?(($|&amp;)([#|$]*[A-Za-z][\w_]*|\{[A-Za-z][\w_]*\}))([\[\{].+[\]\}])*(\([^$&amp;\)]+\))*\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 &lt;import&gt; \
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: &lt;image&gt;/res/adm/includes/templates/man1.jpg&lt;/image&gt;. 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 &amp;fpr_f(), \
its derivative using &amp;fpr_dfdx(), and its second derivative using \
&amp;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 &amp;fpr_val(), \
e.g., &amp;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 ≤ θ &lt; \
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 ≤ θ &lt; 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 &lt;hintpart on="NAME"&gt; &lt;/hintpart&gt; \
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 &lt;hintpart on="default"&gt; &lt;/hintpart&gt; 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:  
        &lt;radiobuttonhint answer="('foil','granite','gabbro')" \
name="igneous"&gt;&lt;/radiobuttonhint&gt;  
        In order to trigger display of this hint you also need to create a \
&lt;hintpart&gt; &lt;/hintpart&gt; 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'= &gt; 'True','Foil2'= &gt; \
'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., &lt; optionhint \
concept="('buoyancy'= &gt; 'correct','density'= &gt; 'correct')" name="fluids" / &gt; \
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 \
&lt;hintpart&gt; 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 &lt;hintpart&gt; 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 &lt;h1&gt;..&lt;h6&gt; \
&lt;font&gt; and &lt;basefont&gt;, 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 &lt;tic&gt; 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 &lt;head&gt;).  
        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 &lt;part&gt; tags with a randomlist tag. Note that when \
randomlist wraps &lt;part&gt; 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 \
&lt;languageblock&gt;...&lt;/languageblock&gt; is rendered If the currently preferred \
language is in the exclude list, the content in the \
&lt;languageblock&gt;..&lt;/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>
        &lt;translated&gt; starts a block of a resource that has multiple \
translations.  See the &lt;lang&gt; tag as well.
        When &lt;/translated&gt; 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 &lt;lang&gt; is only registered for the duration of the \
&lt;translated&gt;...&lt;/translated&gt; 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.:  &lt;lang which='senisoUS' \
                other='senisoCA,senisoAU,seniso'&gt;
        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 &lt;m&gt; 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.  
        &lt;num format="2E"&gt;31454678&lt;/num&gt; results in 3.15 x 10^7
        &lt;num format="2f"&gt;31454678&lt;/num&gt; results in 31454678.00
        &lt;num format="4g"&gt;31454678&lt;/num&gt; results in 3.145 x 10^7
        &lt;num format="4g"&gt;314.54678&lt;/num&gt; results in 314.5
        &lt;num format=",2f"&gt;31454678&lt;/num&gt; results in 31,454,678.00
        &lt;num format="$2f"&gt;31454678&lt;/num&gt; results in $31,454,678.00
        &lt;num format="2s"&gt;31454678&lt;/num&gt; results in 31000000
        &lt;num format=",2s"&gt;31454678&lt;/num&gt; 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 &lt;style&gt; 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 (&lt;html&gt;) 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 (&lt;head&gt;) 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 (&lt;link&gt;) 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 &lt;style&gt; 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 &lt;script&gt; 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 &lt;body&gt; element represents the content of an HTML document. \
There is only one &lt;body&gt; 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 (&lt;section&gt;) represents a generic section of a \
document, i.e., a thematic grouping of content, typically with a heading. Each \
&lt;section&gt; should be identified, typically by including a heading (h1-h6 \
element) as a child of the &lt;section&gt; 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 &lt;header&gt; 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 &lt;footer&gt; or &lt;header&gt; 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 &lt;footer&gt; Element represents a footer for its nearest \
sectioning content or sectioning root element (i.e, its nearest parent \
&lt;article&gt;, &lt;aside&gt;, &lt;nav&gt;, &lt;section&gt;, &lt;blockquote&gt;, \
&lt;body&gt;, &lt;details&gt;, &lt;fieldset&gt;, &lt;figure&gt;, &lt;td&gt;). A \
footer typically contains information about the author of the section, copyright data \
or links to related documents.  
        This element should have no &lt;footer&gt; or &lt;header&gt; 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 &lt;aside&gt; 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 &lt;div&gt; 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 &lt;article&gt; or &lt;nav&gt;) is \
appropriate.  </xs:documentation>
    </xs:annotation>
    <xs:attributeGroup ref="coreattrs"/>
    <xs:attribute name="align">
      <xs:annotation>
        <xs:documentation>
          In HTML5, the align attribute on &lt;div&gt; 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 &lt;p&gt; 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 &lt;li&gt; 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 &lt;ol&gt; 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 &lt;ol&gt; 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 (&lt;ul&gt;) or for menus \
(&lt;menu&gt;).  
          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 &lt;dl&gt; 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, &lt;dl&gt; 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 &lt;dt&gt; element (or HTML Definition Term Element) identifies a \
term in a definition list. This element can occur only as a child element of a \
&lt;dl&gt;. It is usually followed by a &lt;dd&gt; element; however, multiple \
&lt;dt&gt; elements in a row indicate several terms that are all defined by the \
immediate next &lt;dd&gt; element.  </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:complexType mixed="true" name="dd-base">
    <xs:annotation>
      <xs:documentation>
        The HTML &lt;dd&gt; Element (or HTML Description Element) indicates the \
description of a term in a description list (&lt;dl&gt;) element. This element can \
occur only as a child element of a definition list and it must follow a &lt;dt&gt; \
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 (&lt;table&gt;) 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 \
&lt;table&gt; 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 &lt;table&gt; 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 &lt;table&gt; element \
itself, and the property padding on the &lt;td&gt;.  </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 &lt;table&gt; 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 &lt;table&gt; element \
itself, and the property margin on the &lt;td&gt; 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 &lt;table&gt; \
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 &lt;thead&gt;, &lt;tbody&gt; and &lt;tfoot&gt; elements) and between \
                column groups (defined by the &lt;col&gt; and &lt;colgroup&gt; \
                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 \
&lt;thead&gt;, &lt;tbody&gt;, &lt;tfoot&gt;, &lt;col&gt; or &lt;colgroup&gt; \
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 &lt;caption&gt; Element (or HTML Table Caption Element) represents \
the title of a table. Though it is always the first descendant of a &lt;table&gt;, \
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 (&lt;thead&gt;) 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 (&lt;tfoot&gt;) 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 &lt;th&gt; 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 &lt;a&gt; 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 &lt;pre&gt; 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 &lt;code&gt; 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 &lt;kbd&gt; 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 &lt;samp&gt; 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 &lt;cite&gt; 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 &lt;blockquote&gt; or &lt;q&gt; element to \
reference an online resource for a source.  
        
        Style note:
        
        To avoid the default italic style from being used for the &lt;cite&gt; \
element use the CSS font-style property.  </xs:documentation>
    </xs:annotation>
  </xs:element>
  <xs:element name="q">
    <xs:annotation>
      <xs:documentation>
        The HTML &lt;q&gt; 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 \
&lt;blockquote&gt; 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 (&lt;tt&gt;) 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 &lt;code&gt; element.  \
</xs:documentation>  </xs:annotation>
  </xs:element>
  <xs:element name="ins">
    <xs:annotation>
      <xs:documentation>
        The HTML &lt;ins&gt; 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 &lt;del&gt; 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 (&lt;var&gt;) 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 (&lt;small&gt;) 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 &lt;br&gt; 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 &lt;hr&gt; 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 &lt;address&gt; Element may be used by authors to supply contact \
information for its nearest &lt;article&gt; or &lt;body&gt; 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 &lt;blockquote&gt; Element (or HTML Block Quotation Element) \
indicates that the enclosed text is an extended quotation. Usually, this is rendered \
visually by indentation (to change &lt;blockquote&gt; 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 \
&lt;cite&gt; 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 &lt;img&gt; 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 &lt;figure&gt; Element represents self-contained content, frequently \
with a caption (&lt;figcaption&gt;), 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 &lt;figure&gt; element by \
inserting a &lt;figcaption&gt; 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 &lt;figcaption&gt; Element represents a caption or a legend \
associated with a figure or an illustration described by the rest of the data of the \
&lt;figure&gt; element which is its immediate ancestor which means &lt;figcaption&gt; \
can be the first or last element inside a &lt;figure&gt; 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 &lt;object&gt; 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 &lt;map&gt; 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 &lt;embed&gt; 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 &lt;video&gt; element is used to embed video content. It may contain \
several video sources, represented using the src attribute or the &lt;source&gt; \
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 &lt;source&gt; element is used to specify multiple media resources \
for &lt;picture&gt;, &lt;audio&gt; and &lt;video&gt; 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 &lt;audio&gt; element is used to embed sound content in documents. \
It may contain several audio sources, represented using the src attribute or the \
&lt;source&gt; 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 &lt;map&gt; element is used with &lt;area&gt; 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 &lt;area&gt; element defines a hot-spot region on an image, and \
optionally associates it with a hypertext link. This element is used only within a \
&lt;map&gt; 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&lt; 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 &lt;canvas&gt; 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 &lt;canvas&gt; 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 &lt;form&gt; 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 &lt;input&gt; 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 &lt;input&gt; 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 &lt;label&gt; 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 &lt;input&gt; 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 &amp; 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 (&lt;select&gt;) element represents a control that presents a \
menu of options. The options within the menu are represented by &lt;option&gt; \
elements, which can be grouped by &lt;optgroup&gt; 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 &lt;optgroup&gt; element creates a grouping of \
options within a &lt;select&gt; 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 &lt;option&gt; element is used to create a control \
representing an item within a &lt;select&gt;, an &lt;optgroup&gt; or a \
&lt;datalist&gt; 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 &lt;textarea&gt; 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 &lt;fieldset&gt; element is used to group several controls as well \
as labels (&lt;label&gt;) 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 &lt;legend&gt; Element (or HTML Legend Field Element) represents a \
caption for the content of its parent &lt;fieldset&gt;.  </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 &lt;button&gt; 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 &lt;iframe&gt; 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','conceptg \
roup','itemgroup','item','label','data','function','array','unit','answergroup','funct \
ionplotresponse','functionplotruleset','functionplotelements','functionplotcustomrule' \
,'essayresponse','hintpart','formulahint','numericalhint','reactionhint','organichint' \
,'optionhint','radiobuttonhint','stringhint','customhint','mathhint','formulahintcondi \
tion','numericalhintcondition','reactionhintcondition','organichintcondition','optionh \
intcondition','radiobuttonhintcondition','stringhintcondition','customhintcondition',' \
mathhintcondition','imageresponse','foilgroup','datasubmission','textfield','hiddensub \
mission','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','Set \
up','Instance','InstanceText','Criteria','CriteriaText','GraderNote','languageblock',' \
translated','lang','instructorcomment','dataresponse','togglebox','standalone','commen \
t','drawimage','allow','displayduedate','displaytitle','responseparam','organicstructu \
re','scriptlib','parserlib','drawoptionlist','spline','backgroundplot','plotobject','p \
lotvector','drawvectorsum','functionplotrule','functionplotvectorrule','functionplotve \
ctorsumrule','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','essayrespo \
nse','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',@responses,'foil','item','text','hintgroup','hintpart','label','p \
art','preduedate','postanswerdate','solved','notsolved','block','while','web','standal \
one','problemtype','languageblock','translated','lang','window','windowlink','togglebo \
x','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','e \
ndoutttext','endouttxt','endouutext','ednouttext','endouttex','endoouttext','endouttes \
t','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','come \
from_list','copyright','correct','count','course','course_list','courserestricted','cr \
eationdate','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','lastr \
evisiondate','lowestgradelevel','middlename','mime','modifyinguser','notes','owner','p \
ermanentemail','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/&/&amp;/g;
      $math =~ s/</&lt;/g;
      $math =~ s/>/&gt;/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 &nbsp; 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','postansw \
erdate','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 &lt; and &gt; 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).'&lt;'.substr($line, $indinf+1);
            $lines->[$i] = $line;
          }
        } elsif ($indinf != -1 && $indsup == -1) {
          $line = substr($line, 0, $indinf).'&lt;'.substr($line, $indinf+1);
          $lines->[$i] = $line;
        } elsif ($indsup != -1 && ($indinf == -1 || $indsup < $indinf)) {
          $line = substr($line, 0, $indsup).'&gt;'.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/&#128;|&#x80;/€/g;
    $line =~ s/&#130;|&#x82;/‚/g;
    $line =~ s/&#132;|&#x84;/„/g;
    $line =~ s/&#133;|&#x85;/…/g;
    $line =~ s/&#134;|&#x86;/†/g;
    $line =~ s/&#135;|&#x87;/‡/g;
    $line =~ s/&#136;|&#x88;/ˆ/g;
    $line =~ s/&#137;|&#x89;/‰/g;
    $line =~ s/&#139;|&#x8B;/‹/g;
    $line =~ s/&#145;|&#x91;/‘/g;
    $line =~ s/&#146;|&#x92;/’/g;
    $line =~ s/&#147;|&#x93;/“/g;
    $line =~ s/&#148;|&#x94;/”/g;
    $line =~ s/&#149;|&#x95;/•/g;
    $line =~ s/&#150;|&#x96;/–/g;
    $line =~ s/&#151;|&#x97;/—/g;
    $line =~ s/&#152;|&#x98;/˜/g;
    $line =~ s/&#153;|&#x99;/™/g;
    $line =~ s/&#155;|&#x9B;/›/g;
    $line =~ s/&#156;|&#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:
        &lt;schema . . .>
         . . .
         &lt;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.

        &lt;type . . .>
         . . .
         &lt;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�2NjF�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@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@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:uni0391uni0392uni0393uni0394u \
ni0395uni0396uni0397uni0398uni0399uni039Auni039Buni039Cuni039Duni039Euni039F \
uni03A0uni03A1uni03A3uni03A4uni03A5uni03A6uni03A7uni03A8uni03A9uni03B1uni03 \
B2uni03B3uni03B4uni03B5uni03B6uni03B7uni03B8uni03B9uni03BAuni03BBuni03BCuni \
03BDuni03BEuni03BFuni03C0uni03C1uni03C2uni03C3uni03C4uni03C5uni03C6uni03C7uni03C8uni03C9uni03D0uni03D1uni03D2uni03D5uni03D6uni03D8uni03D9uni03DA



��


��
��
��
��

��


��
��
��




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@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}""�vs \
7�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@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��}���bh \
L[�[��"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@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@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,�ʼn|�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@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��H \
P�"��::��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@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���4 \
0ij��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@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�؀Dž��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�ADŽ�(���@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@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(����-���4 \
F`]�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-@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@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@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@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@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@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@PjP�P�QQDQ�Q�R
 RDR�R�SStS�S�T>T|T�T�U.UZUpU�U�V
m@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@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@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@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@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@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@AH6]8RaHl��H^��gaR8]6HA@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:;<=>?@ABCDEFGHIJK \
LMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopuni00A0uni \
00ADuni0100uni0101uni0102uni0103uni0104uni0105uni0106uni0107uni0108uni0109u \
ni010Auni010Buni010Cuni010Duni010Euni010Funi0110uni0111uni0112uni0113uni0116 \
uni0117uni011Cuni011Duni0120uni0121uni0122uni0124uni0125uni0126uni0127uni01 \
28uni0129uni012Auni012Buni0130uni0132uni0133uni0134uni0135uni0138uni013Duni \
013Euni013Funi0140uni014Auni014Buni014Cuni014Duni0150uni0151uni015Auni015Bu \
ni015Cuni015Duni0165uni0166uni0167uni0168uni0169uni016Auni016Buni016Euni016F \
uni0170uni0171uni0174uni0175uni0176uni0177uni0179uni017Auni017Buni017Cuni01 \
7Funi0180uni0188uni0190uni0195uni0199uni019Auni019Buni019Euni01A0uni01A1uni \
01A5uni01AAuni01ABuni01ADuni01B5uni01BAuni01BBuni01BEuni01C0uni01C1uni01C2u!
  ni01C3uni01F0uni0221uni02B9uni02BAuni02BBuni02BCuni02BDuni02BEuni02BFuni02 \
C2uni02C3uni02C4uni02C5uni02C8uni02C9uni02CAuni02CBuni02CCuni02CDuni02CEuni \
02CFuni02D6uni02D7uni02DFuni02ECuni02EDuni02F7uni0300uni0301uni0302uni0303u \
ni0304uni0305uni0306uni0307uni0308uni030Auni030Buni030Cuni030Duni030Euni030F \
uni0311uni0312uni0313uni0314uni0315uni031Auni031Buni031Duni031Euni031Funi03 \
20uni0327uni033Funi0359uni035Cuni0360uni0361uni0362uni037Euni0384uni0385uni \
0387uni0391uni0392uni0393uni0394uni0395uni0396uni0397uni0398uni0399uni039Au \
ni039Buni039Cuni039Duni039Euni039Funi03A0uni03A1uni03A3uni03A4uni03A5uni03A6 \
uni03A7uni03A8uni03A9uni03AAuni03ABuni03ACuni03ADuni03AEuni03AFuni03B0uni03 \
B1uni03B2uni03B3uni03B4uni03B5uni03B6uni03B7uni03B8uni03B9uni03BAuni03BBuni \
03BCuni03BDuni03BEuni03BFuni03C0uni03C1uni03C2uni03C3uni03C4uni03C5uni03C6uni03C7uni03C8uni03C9uni03CAuni03CBuni03!
  CCuni03CDuni03CEuni03D0uni03D1uni03D2uni03D5uni03D6uni03D8uni
03D9uni03DAuni03DBuni03DCuni03DDuni03DEuni03DFuni03E0uni03E1uni03F0uni03F1u \
ni03F4uni03F5uni03F6uni0401uni0402uni0403uni0404uni0405uni0406uni0407uni0408 \
uni0409uni040Auni040Buni040Cuni040Euni040Funi0410uni0411uni0412uni0413uni04 \
14uni0415uni0416uni0417uni0418uni0419uni041Auni041Buni041Cuni041Duni041Euni \
041Funi0420uni0421uni0422uni0423uni0424uni0425uni0426uni0427uni0428uni0429u \
ni042Auni042Buni042Cuni042Duni042Euni042Funi0430uni0431uni0432uni0433uni0434 \
uni0435uni0436uni0437uni0438uni0439uni043Auni043Buni043Cuni043Duni043Euni04 \
3Funi0440uni0441uni0442uni0443uni0444uni0445uni0446uni0447uni0448uni0449uni \
044Auni044Buni044Cuni044Duni044Euni044Funi0451uni0452uni0453uni0454uni0455u \
ni0456uni0457uni0458uni0459uni045Auni045Buni045Cuni045Euni045Funi0462uni0463uni046Auni046Buni0472uni0473uni0474uni0475uni0490uni0491uni2010uni2011
 figuredashuni2015uni2016uni2017uni201Buni201Ftwodotenleaderuni2031uni2032uni \
2033uni2034uni2035uni2036uni2037uni2038uni203Euni2040uni2043uni204Euni2052u \
ni205F	nsuperioruni20ACuni20D0uni20D1uni20D2uni20D6uni20D7uni20DBuni20DCuni20 \
DDuni20DEuni20E1uni20E5uni20E6uni20E7uni20E8uni20E9uni20EAuni20EBuni20EEuni \
20EFuni20F0uni2102uni2107uni210Auni210Buni210Cuni210Duni210Euni210Funi2110u \
ni2111uni2112uni2113uni2115uni2116uni2118uni2119uni211Auni211Buni211Cuni211D \
uni211Euni2124uni2125uni2126uni2127uni2128uni2129uni212Buni212Cuni212Duni21 \
2Euni212Funi2130uni2131uni2132uni2133uni2134uni2135uni2136uni2137uni2138uni \
213Cuni213Duni213Euni213Funi2140uni2145uni2146uni2147uni2148uni2149onethird	 \
twothirdsuni2155uni2156uni2157uni2158uni2159uni215Auni2190uni2191uni2192uni2 \
193uni2194uni2195uni2196uni2197uni2198uni2199uni219Auni219Buni21A4uni21A5uni21A6uni21A7uni21AEuni21B0uni21B1uni21B!
  2uni21B3uni21B4uni21B5uni21B6uni21B7uni21B9uni21BAuni21BBuni21C4uni21CDun \
i21CEuni21CFuni21D0uni21D1uni21D2uni21D3uni21D4uni21D5uni21E0uni21E1uni21E2 \
uni21E3uni21E4uni21E5uni21E6uni21E7uni21E8uni21E9uni21F5uni21F7uni21F8uni21F \
9uni21FAuni21FDuni21FEuni21FFuni2200uni2201uni2202uni2203uni2204uni2205uni2 \
206uni2207uni2208uni2209uni220Auni220Buni220Cuni220Duni220Funi2210uni2211un \
i2213uni2214uni2215uni2216uni2217uni2218uni2219uni221Duni221Euni221Funi2220 \
uni2221uni2222uni2223uni2224uni2225uni2226uni2227uni2228uni2229uni222Auni222 \
Buni222Cuni222Duni222Euni222Funi2230uni2231uni2232uni2233uni2234uni2235uni2 \
236uni2237uni2238uni223Buni223Cuni223Duni223Euni223Funi2240uni2241uni2242un \
i2243uni2244uni2245uni2246uni2247uni2248uni2249uni224Auni224Buni224Cuni224D \
uni225Duni2260uni2261uni2262uni2263uni2264uni2265uni2266uni2267uni2268uni2269uni226Auni226Buni226Cuni226Duni226Eun!
  i226Funi2270uni2271uni2272uni2273uni2274uni2275uni2276uni2277
uni2278uni2279uni227Auni227Buni227Cuni227Duni227Euni227Funi2280uni2281uni228 \
2uni2283uni2284uni2285uni2286uni2287uni2288uni2289uni228Auni228Buni228Funi2 \
290uni2291uni2292uni2293uni2294uni2295uni2296uni2297uni2298uni2299uni22A2un \
i22A3uni22A4uni22A5uni22A6uni22A7uni22A8uni22A9uni22ACuni22B2uni22B3uni22BA \
uni22BEuni22BFuni22C0uni22C1uni22C2uni22C3uni22C4uni22C5uni22C6uni22CEuni22C \
Funi22D5uni22DAuni22DBuni22DCuni22DDuni22DEuni22DFuni22E0uni22E1uni2300uni2 \
305uni2308uni2309uni230Auni230Buni2312uni2316uni2320uni2321uni2322uni2323un \
i2329uni232Auni233Funi2340uni2393uni239Buni239Cuni239Duni239Euni239Funi23A0 \
uni23A1uni23A2uni23A3uni23A4uni23A5uni23A6uni23A7uni23A8uni23A9uni23AAuni23A \
Buni23ACuni23ADuni23AEuni23AFuni23B0uni23B1uni23CEuni23D0uni23DCuni23DDuni2 \
3DEuni23DFuni23E0uni23E1uni23E4uni2605uni2606uni2609uni263Cuni263Duni263Euni263Funi2640uni2641uni2642uni2643uni26!
  44uni2646uni2647uni2648uni2649uni26E2uni279Buni27F5uni27F6uni27F7uni27F8u \
ni27F9uni27FAuni27FBuni27FCuni27FDuni27FEuni2902uni2903uni2904uni2906uni2907 \
uni2912uni2913uni2934uni2935uni2936uni2937uni2938uni2939uni293Auni293Buni29 \
3Cuni293Duni293Euni293Funi2940uni2941uni2981uni299Buni299Cuni299Duni299Euni \
299Funi29A0uni29A1uni29A8uni29A9uni29AAuni29ABuni29ACuni29ADuni29AEuni29AFu \
ni29BBuni29BFuni29C0uni29C1uni29E3uni29E7uni29FAuni29FCuni29FDuni2A00uni2A01 \
uni2A02uni2A03uni2A04uni2A05uni2A06uni2A09uni2A0Auni2A0Buni2A0Cuni2A0Duni2A \
0Euni2A0Funi2A20uni2A2Funi2A3Funi2A42uni2A43uni2A7Duni2A7Euni2A95uni2A96uni \
2A99uni2A9Auni2A9Buni2A9Cuni2AFDuni2B50uni2B51uniA727u1D49Cu1D49Eu1D49Fu1D4 \
A2u1D4A5u1D4A6u1D4A9u1D4AAu1D4ABu1D4ACu1D4AEu1D4AFu1D4B0u1D4B1u1D4B2u1D4B3 \
u1D4B4u1D4B5u1D4B6u1D4B7u1D4B8u1D4B9u1D4BBu1D4BDu1D4BEu1D4BFu1D4C0u1D4C1u1D4C2u1D4C3u1D4C5u1D4C6u1D4C7u1D4C8u1D!
  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@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@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@PjP�P�QQDQ�Q�R
 RDR�R�SStS�S�T>T|T�T�U.UZUpU�U�V
m@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@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@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@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@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@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@AH6]8RaHl��H^��gaR8]6HA@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:;<=>?@ABCDEFGHIJK \
LMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopuni00A0uni \
00ADuni0100uni0101uni0102uni0103uni0104uni0105uni0106uni0107uni0108uni0109u \
ni010Auni010Buni010Cuni010Duni010Euni010Funi0110uni0111uni0112uni0113uni0116 \
uni0117uni011Cuni011Duni0120uni0121uni0122uni0124uni0125uni0126uni0127uni01 \
28uni0129uni012Auni012Buni0130uni0132uni0133uni0134uni0135uni0138uni013Duni \
013Euni013Funi0140uni014Auni014Buni014Cuni014Duni0150uni0151uni015Auni015Bu \
ni015Cuni015Duni0165uni0166uni0167uni0168uni0169uni016Auni016Buni016Euni016F \
uni0170uni0171uni0174uni0175uni0176uni0177uni0179uni017Auni017Buni017Cuni01 \
7Funi0180uni0188uni0190uni0195uni0199uni019Auni019Buni019Euni01A0uni01A1uni \
01A5uni01AAuni01ABuni01ADuni01B5uni01BAuni01BBuni01BEuni01C0uni01C1uni01C2u!
  ni01C3uni01F0uni0221uni02B9uni02BAuni02BBuni02BCuni02BDuni02BEuni02BFuni02 \
C2uni02C3uni02C4uni02C5uni02C8uni02C9uni02CAuni02CBuni02CCuni02CDuni02CEuni \
02CFuni02D6uni02D7uni02DFuni02ECuni02EDuni02F7uni0300uni0301uni0302uni0303u \
ni0304uni0305uni0306uni0307uni0308uni030Auni030Buni030Cuni030Duni030Euni030F \
uni0311uni0312uni0313uni0314uni0315uni031Auni031Buni031Duni031Euni031Funi03 \
20uni0327uni033Funi0359uni035Cuni0360uni0361uni0362uni037Euni0384uni0385uni \
0387uni0391uni0392uni0393uni0394uni0395uni0396uni0397uni0398uni0399uni039Au \
ni039Buni039Cuni039Duni039Euni039Funi03A0uni03A1uni03A3uni03A4uni03A5uni03A6 \
uni03A7uni03A8uni03A9uni03AAuni03ABuni03ACuni03ADuni03AEuni03AFuni03B0uni03 \
B1uni03B2uni03B3uni03B4uni03B5uni03B6uni03B7uni03B8uni03B9uni03BAuni03BBuni \
03BCuni03BDuni03BEuni03BFuni03C0uni03C1uni03C2uni03C3uni03C4uni03C5uni03C6uni03C7uni03C8uni03C9uni03CAuni03CBuni03!
  CCuni03CDuni03CEuni03D0uni03D1uni03D2uni03D5uni03D6uni03D8uni
03D9uni03DAuni03DBuni03DCuni03DDuni03DEuni03DFuni03E0uni03E1uni03F0uni03F1u \
ni03F4uni03F5uni03F6uni0401uni0402uni0403uni0404uni0405uni0406uni0407uni0408 \
uni0409uni040Auni040Buni040Cuni040Euni040Funi0410uni0411uni0412uni0413uni04 \
14uni0415uni0416uni0417uni0418uni0419uni041Auni041Buni041Cuni041Duni041Euni \
041Funi0420uni0421uni0422uni0423uni0424uni0425uni0426uni0427uni0428uni0429u \
ni042Auni042Buni042Cuni042Duni042Euni042Funi0430uni0431uni0432uni0433uni0434 \
uni0435uni0436uni0437uni0438uni0439uni043Auni043Buni043Cuni043Duni043Euni04 \
3Funi0440uni0441uni0442uni0443uni0444uni0445uni0446uni0447uni0448uni0449uni \
044Auni044Buni044Cuni044Duni044Euni044Funi0451uni0452uni0453uni0454uni0455u \
ni0456uni0457uni0458uni0459uni045Auni045Buni045Cuni045Euni045Funi0462uni0463uni046Auni046Buni0472uni0473uni0474uni0475uni0490uni0491uni2010uni2011
 figuredashuni2015uni2016uni2017uni201Buni201Ftwodotenleaderuni2031uni2032uni \
2033uni2034uni2035uni2036uni2037uni2038uni203Euni2040uni2043uni204Euni2052u \
ni205F	nsuperioruni20ACuni20D0uni20D1uni20D2uni20D6uni20D7uni20DBuni20DCuni20 \
DDuni20DEuni20E1uni20E5uni20E6uni20E7uni20E8uni20E9uni20EAuni20EBuni20EEuni \
20EFuni20F0uni2102uni2107uni210Auni210Buni210Cuni210Duni210Euni210Funi2110u \
ni2111uni2112uni2113uni2115uni2116uni2118uni2119uni211Auni211Buni211Cuni211D \
uni211Euni2124uni2125uni2126uni2127uni2128uni2129uni212Buni212Cuni212Duni21 \
2Euni212Funi2130uni2131uni2132uni2133uni2134uni2135uni2136uni2137uni2138uni \
213Cuni213Duni213Euni213Funi2140uni2145uni2146uni2147uni2148uni2149onethird	 \
twothirdsuni2155uni2156uni2157uni2158uni2159uni215Auni2190uni2191uni2192uni2 \
193uni2194uni2195uni2196uni2197uni2198uni2199uni219Auni219Buni21A4uni21A5uni21A6uni21A7uni21AEuni21B0uni21B1uni21B!
  2uni21B3uni21B4uni21B5uni21B6uni21B7uni21B9uni21BAuni21BBuni21C4uni21CDun \
i21CEuni21CFuni21D0uni21D1uni21D2uni21D3uni21D4uni21D5uni21E0uni21E1uni21E2 \
uni21E3uni21E4uni21E5uni21E6uni21E7uni21E8uni21E9uni21F5uni21F7uni21F8uni21F \
9uni21FAuni21FDuni21FEuni21FFuni2200uni2201uni2202uni2203uni2204uni2205uni2 \
206uni2207uni2208uni2209uni220Auni220Buni220Cuni220Duni220Funi2210uni2211un \
i2213uni2214uni2215uni2216uni2217uni2218uni2219uni221Duni221Euni221Funi2220 \
uni2221uni2222uni2223uni2224uni2225uni2226uni2227uni2228uni2229uni222Auni222 \
Buni222Cuni222Duni222Euni222Funi2230uni2231uni2232uni2233uni2234uni2235uni2 \
236uni2237uni2238uni223Buni223Cuni223Duni223Euni223Funi2240uni2241uni2242un \
i2243uni2244uni2245uni2246uni2247uni2248uni2249uni224Auni224Buni224Cuni224D \
uni225Duni2260uni2261uni2262uni2263uni2264uni2265uni2266uni2267uni2268uni2269uni226Auni226Buni226Cuni226Duni226Eun!
  i226Funi2270uni2271uni2272uni2273uni2274uni2275uni2276uni2277
uni2278uni2279uni227Auni227Buni227Cuni227Duni227Euni227Funi2280uni2281uni228 \
2uni2283uni2284uni2285uni2286uni2287uni2288uni2289uni228Auni228Buni228Funi2 \
290uni2291uni2292uni2293uni2294uni2295uni2296uni2297uni2298uni2299uni22A2un \
i22A3uni22A4uni22A5uni22A6uni22A7uni22A8uni22A9uni22ACuni22B2uni22B3uni22BA \
uni22BEuni22BFuni22C0uni22C1uni22C2uni22C3uni22C4uni22C5uni22C6uni22CEuni22C \
Funi22D5uni22DAuni22DBuni22DCuni22DDuni22DEuni22DFuni22E0uni22E1uni2300uni2 \
305uni2308uni2309uni230Auni230Buni2312uni2316uni2320uni2321uni2322uni2323un \
i2329uni232Auni233Funi2340uni2393uni239Buni239Cuni239Duni239Euni239Funi23A0 \
uni23A1uni23A2uni23A3uni23A4uni23A5uni23A6uni23A7uni23A8uni23A9uni23AAuni23A \
Buni23ACuni23ADuni23AEuni23AFuni23B0uni23B1uni23CEuni23D0uni23DCuni23DDuni2 \
3DEuni23DFuni23E0uni23E1uni23E4uni2605uni2606uni2609uni263Cuni263Duni263Euni263Funi2640uni2641uni2642uni2643uni26!
  44uni2646uni2647uni2648uni2649uni26E2uni279Buni27F5uni27F6uni27F7uni27F8u \
ni27F9uni27FAuni27FBuni27FCuni27FDuni27FEuni2902uni2903uni2904uni2906uni2907 \
uni2912uni2913uni2934uni2935uni2936uni2937uni2938uni2939uni293Auni293Buni29 \
3Cuni293Duni293Euni293Funi2940uni2941uni2981uni299Buni299Cuni299Duni299Euni \
299Funi29A0uni29A1uni29A8uni29A9uni29AAuni29ABuni29ACuni29ADuni29AEuni29AFu \
ni29BBuni29BFuni29C0uni29C1uni29E3uni29E7uni29FAuni29FCuni29FDuni2A00uni2A01 \
uni2A02uni2A03uni2A04uni2A05uni2A06uni2A09uni2A0Auni2A0Buni2A0Cuni2A0Duni2A \
0Euni2A0Funi2A20uni2A2Funi2A3Funi2A42uni2A43uni2A7Duni2A7Euni2A95uni2A96uni \
2A99uni2A9Auni2A9Buni2A9Cuni2AFDuni2B50uni2B51uniA727u1D49Cu1D49Eu1D49Fu1D4 \
A2u1D4A5u1D4A6u1D4A9u1D4AAu1D4ABu1D4ACu1D4AEu1D4AFu1D4B0u1D4B1u1D4B2u1D4B3 \
u1D4B4u1D4B5u1D4B6u1D4B7u1D4B8u1D4B9u1D4BBu1D4BDu1D4BEu1D4BFu1D4C0u1D4C1u1D4C2u1D4C3u1D4C5u1D4C6u1D4C7u1D4C8u1D!
  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("&", "&amp;");
    doc = doc.replaceAll("<", "&lt;");
    doc = doc.replaceAll(">", "&gt;");
    /*
    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('&', '&amp;');
    s = s.replaceAll('"', '&quot;');
    //s = s.replaceAll("'", '&apos;');
    s = s.replaceAll('<', '&lt;');
    s = s.replaceAll('>', '&gt;');
    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>{
      '&' : '&amp;',
      '"' : '&quot;',
      '<' : '&lt;',
      '>' : '&gt;'
    };
    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;
   



[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic