[LON-CAPA-cvs] cvs: modules /damieng/clean_xml clean_xml.pl html_to_xml.pm loncapa.xsd post_xml.pm pre_xml.pm validate_xml.pl xml.xsd /damieng/graphical_editor/daxe pubspec.lock pubspec.yaml /damieng/graphical_editor/daxe/.settings org.eclipse.core.resources.prefs /damieng/graphical_editor/daxe/lib LocalStrings_en.properties LocalStrings_fr.properties daxe.css daxe.dart /damieng/graphical_editor/daxe/lib/fonts STIXSubset-Bold.eot STIXSubset-Bold.ttf STIXSubset-Italic.eot STIXSubset-Italic.ttf STIXSubset-Regular.eot STIXSubset-Regular.ttf /damieng/graphical_editor/daxe/lib/images attributes.png bullet1.png bullet2.png close_dialog.png collapsed_tree.png expanded_tree.png mergebottom.png mergeright.png splitx.png splity.png /damieng/graphical_editor/daxe/lib/images/toolbar add_link.png align_center.png align_justify.png align_left.png align_right.png anchor.png document_save.png equation.png find.png history_redo.png history_undo.png insert_image.png insert_symbol.png insert_t!able.png list_lower_level.png list_rise_level.png ol.png remove_link.png remove_styles.png spellcheck.png style_bold.png style_color.png style_italic.png style_strikethrough.png style_subscript.png style_superscript.png style_underline.png ul.png /damieng/graphical_editor/daxe/lib/src attribute_dialog.dart config.dart css_map.dart cursor.dart daxe_attr.dart daxe_document.dart daxe_exception.dart daxe_node.dart file_open_dialog.dart find_dialog.dart help_dialog.dart insert_panel.dart interface_schema.dart left_offset_position.dart left_panel.dart locale.dart menu.dart menu_item.dart menubar.dart node_factory.dart node_offset_position.dart position.dart right_offset_position.dart simple_schema.dart source_window.dart strings.dart tag.dart toolbar.dart toolbar_box.dart toolbar_button.dart toolbar_item.dart toolbar_menu.dart toolbar_style_info.dart tree_item.dart tree_panel.dart undoable_edit.dart unknown_element_dialog.dart validation_dialog.dart web_page.dart /damieng/graph!ical_editor/daxe/lib/src/equations equation_dialog.dart equations.dart math_base.dart string_math_builder.dart text_metrics.dart /damieng/graphical_editor/daxe/lib/src/equations/elements math_element.dart math_frac.dart math_identifier.dart math_number.dart math_operator.dart math_over.dart math_phantom.dart math_root.dart
damieng
damieng at source.lon-capa.org
Fri Apr 17 11:35:10 EDT 2015
damieng Fri Apr 17 15:35:10 2015 EDT
Added files:
/modules/damieng/clean_xml clean_xml.pl html_to_xml.pm loncapa.xsd
post_xml.pm pre_xml.pm validate_xml.pl
xml.xsd
/modules/damieng/graphical_editor/daxe pubspec.lock pubspec.yaml
/modules/damieng/graphical_editor/daxe/.settings
org.eclipse.core.resources.prefs
/modules/damieng/graphical_editor/daxe/lib
LocalStrings_en.properties
LocalStrings_fr.properties
daxe.css daxe.dart
/modules/damieng/graphical_editor/daxe/lib/fonts
STIXSubset-Bold.eot
STIXSubset-Bold.ttf
STIXSubset-Italic.eot
STIXSubset-Italic.ttf
STIXSubset-Regular.eot
STIXSubset-Regular.ttf
/modules/damieng/graphical_editor/daxe/lib/images attributes.png
bullet1.png
bullet2.png
close_dialog.png
collapsed_tree.png
expanded_tree.png
mergebottom.png
mergeright.png
splitx.png
splity.png
/modules/damieng/graphical_editor/daxe/lib/images/toolbar
add_link.png
align_center.png
align_justify.png
align_left.png
align_right.png
anchor.png
document_save.png
equation.png
find.png
history_redo.png
history_undo.png
insert_image.png
insert_symbol.png
insert_table.png
list_lower_level.png
list_rise_level.png
ol.png
remove_link.png
remove_styles.png
spellcheck.png
style_bold.png
style_color.png
style_italic.png
style_strikethrough.png
style_subscript.png
style_superscript.png
style_underline.png
ul.png
/modules/damieng/graphical_editor/daxe/lib/src
attribute_dialog.dart
config.dart
css_map.dart
cursor.dart
daxe_attr.dart
daxe_document.dart
daxe_exception.dart
daxe_node.dart
file_open_dialog.dart
find_dialog.dart
help_dialog.dart
insert_panel.dart
interface_schema.dart
left_offset_position.dart
left_panel.dart
locale.dart
menu.dart
menu_item.dart
menubar.dart
node_factory.dart
node_offset_position.dart
position.dart
right_offset_position.dart
simple_schema.dart
source_window.dart
strings.dart
tag.dart
toolbar.dart
toolbar_box.dart
toolbar_button.dart
toolbar_item.dart
toolbar_menu.dart
toolbar_style_info.dart
tree_item.dart
tree_panel.dart
undoable_edit.dart
unknown_element_dialog.dart
validation_dialog.dart
web_page.dart
/modules/damieng/graphical_editor/daxe/lib/src/equations
equation_dialog.dart
equations.dart
math_base.dart
string_math_builder.dart
text_metrics.dart
/modules/damieng/graphical_editor/daxe/lib/src/equations/elements
math_element.dart
math_frac.dart
math_identifier.dart
math_number.dart
math_operator.dart
math_over.dart
math_phantom.dart
math_root.dart
math_root_element.dart
math_row.dart
math_sqrt.dart
math_sub.dart
math_sub_sup.dart
math_sup.dart
math_table.dart
math_table_data.dart
math_table_row.dart
math_text.dart
math_under.dart
math_under_over.dart
/modules/damieng/graphical_editor/daxe/lib/src/nodes dn_anchor.dart
dn_area.dart
dn_cdata.dart
dn_comment.dart
dn_division.dart
dn_document.dart
dn_empty.dart
dn_equa_tex_mem.dart
dn_equation_mem.dart
dn_file.dart
dn_form.dart
dn_form_field.dart
dn_hidden_div.dart
dn_hidden_p.dart
dn_hr.dart
dn_item.dart
dn_line_break.dart
dn_list.dart
dn_processing_instruction.dart
dn_simple_type.dart
dn_special.dart
dn_string.dart
dn_style.dart
dn_style_span.dart
dn_table.dart
dn_text.dart
dn_witem.dart
dn_wlist.dart
nodes.dart
parent_updating_dn_text.dart
simple_type_control.dart
/modules/damieng/graphical_editor/daxe/lib/src/wxs daxe_wxs.dart
parent.dart
with_sub_elements.dart
wxs.dart
wxs_all.dart
wxs_annotated.dart
wxs_annotation.dart
wxs_any.dart
wxs_attribute.dart
wxs_attribute_group.dart
wxs_choice.dart
wxs_complex_content.dart
wxs_complex_type.dart
wxs_documentation.dart
wxs_element.dart
wxs_exception.dart
wxs_explicit_group.dart
wxs_extension.dart
wxs_facet.dart
wxs_field.dart
wxs_group.dart
wxs_import.dart
wxs_include.dart
wxs_key.dart
wxs_keybase.dart
wxs_keyref.dart
wxs_list.dart
wxs_redefine.dart
wxs_restriction.dart
wxs_schema.dart
wxs_selector.dart
wxs_sequence.dart
wxs_simple_content.dart
wxs_simple_type.dart
wxs_thing.dart
wxs_type.dart
wxs_union.dart
wxs_unique.dart
/modules/damieng/graphical_editor/daxe/lib/src/xmldom attr.dart
attr_impl.dart
cdata_section.dart
cdata_section_impl.dart
comment.dart
comment_impl.dart
document.dart
document_fragment.dart
document_fragment_impl.dart
document_impl.dart
document_type.dart
document_type_impl.dart
dom_exception.dart
dom_implementation.dart
dom_implementation_impl.dart
dom_parser.dart
element.dart
element_impl.dart
entity_reference.dart
entity_reference_impl.dart
node.dart
node_impl.dart
processing_instruction.dart
processing_instruction_impl.dart
text.dart
text_impl.dart
xmldom.dart
/modules/damieng/graphical_editor/daxe/lib/src/xmldom/parser
engine.dart
match_result.dart
state_change.dart
state_condition.dart
token.dart
token_char.dart
token_choice.dart
token_id.dart
token_item.dart
token_repeat.dart
token_rule.dart
token_sequence.dart
xml_parser.dart
/modules/damieng/graphical_editor/daxe/web daxe.dart daxe.html
/modules/damieng/graphical_editor/daxe/web/config XHTML_config.xml
XPAGES.xsd
XPAGES_config.xml
xhtml1-strict.xsd
xml.xsd
/modules/damieng/graphical_editor/loncapa_daxe build.sh daxe.html
pubspec.lock
pubspec.yaml
/modules/damieng/graphical_editor/loncapa_daxe/.settings
org.eclipse.core.resources.prefs
/modules/damieng/graphical_editor/loncapa_daxe/web
LC_math_editor.min.js
LocalStrings_en.properties
LocalStrings_fr.properties
lcd_button.dart
lcd_strings.dart
loncapa_daxe.css
loncapa_daxe.dart
loncapa_daxe.html
parameters.xml
templates.xml
/modules/damieng/graphical_editor/loncapa_daxe/web/config
XHTML_config.xml
loncapa.xsd
loncapa_config.xml
xhtml1-strict.xsd
xml.xsd
/modules/damieng/graphical_editor/loncapa_daxe/web/images
block_collapsed.png
block_editable.png
block_normal.png
delete.png
grip.png
tex.png
/modules/damieng/graphical_editor/loncapa_daxe/web/nodes
hintgroup.dart
lcd_block.dart
lcd_parameter.dart
lm.dart
option_foil.dart
option_foilgroup.dart
option_response.dart
radio_foil.dart
radio_foilgroup.dart
radio_response.dart
rank_foil.dart
rank_foilgroup.dart
rank_response.dart
script_block.dart
simple_ui_text.dart
tex_mathjax.dart
/modules/damieng/graphical_editor/loncapa_daxe/web/templates/problems
ClickImageExample.problem.xml
CustomResponse.problem.xml
DropBox.problem.xml
Essay.problem.xml
HintFormula.problem.xml
HintMathResponse.problem.xml
MultipleAnswerEither.problem.xml
MultipleAnswerUnordered.problem.xml
Plot_curve.problem.xml
Plot_data.problem.xml
RadioResponse.problem.xml
RandomLabelExample.problem.xml
Rnumerical.problem.xml
SelectFromOptions-4ConceptGoups.problem.xml
SelectFromOptions-5ConceptGoups.problem.xml
SelectFromOptions-6ConceptGoups.problem.xml
SelectFromOptions-7ConceptGoups.problem.xml
SelectFromOptions-8ConceptGoups.problem.xml
SelectFromOptions-Simple.problem.xml
SelectFromOptions-multilingual.problem.xml
SimpleFormula.problem.xml
SimpleFormulaCAS.problem.xml
SimpleMatching.problem.xml
SimpleMathResponse.problem.xml
SimpleMathResponseR.problem.xml
SimpleRank.problem.xml
SimpleTrueFalse.problem.xml
StringResponse.problem.xml
answerdependent.problem.xml
blank.problem.xml
custom_equation.problem.xml
customhints.problem.xml
custompartial.problem.xml
customunit.problem.xml
dynamicgraph.problem.xml
examupload.problem.xml
extreme.problem.xml
functionplotback.problem.xml
functionplotone.problem.xml
functionplottwo.problem.xml
functionplotvector.problem.xml
man1.jpg
numMultiAnswerUnordered.problem.xml
numPrePro.problem.xml
numerical.problem.xml
organic.problem.xml
organic_hint.problem.xml
randomvalueradio.problem.xml
reaction.problem.xml
reaction_hint.problem.xml
sampleexternal.problem.xml
stringPrePro.problem.xml
/modules/damieng/graphical_editor/loncapa_daxe/web/templates/responses
customresponse.xml
essayresponse.xml
externalresponse.xml
formularesponse.xml
functionplotresponse.xml
imageresponse.xml
man1.jpg
matchresponse.xml
mathresponse.xml
numericalresponse.xml
optionresponse.xml
organicresponse.xml
radiobuttonresponse.xml
rankresponse.xml
reactionresponse.xml
stringresponse.xml
Log:
added clean_xml and graphical_editor
-------------- next part --------------
Index: modules/damieng/clean_xml/clean_xml.pl
+++ modules/damieng/clean_xml/clean_xml.pl
#!/usr/bin/perl
use strict;
use utf8;
use warnings;
use File::Basename;
use Try::Tiny;
use lib dirname(__FILE__);
use pre_xml;
use html_to_xml;
use post_xml;
binmode(STDOUT, ':encoding(UTF-8)');
if (scalar(@ARGV) != 1) {
print STDERR "Usage: perl clean_xml.pl file|directory\n";
exit(1);
}
# find the command-line argument encoding
use I18N::Langinfo qw(langinfo CODESET);
my $codeset = langinfo(CODESET);
use Encode qw(decode);
@ARGV = map { decode $codeset, $_ } @ARGV;
my $pathname = "$ARGV[0]";
if (-d "$pathname") {
$pathname =~ s/\/$//;
my $start = time();
my ($converted, $failures) = convert_dir($pathname);
my $end = time();
my $elapsed = $end - $start;
my $minutes = int($elapsed / 60);
my $seconds = $elapsed - ($minutes*60);
print "\n".scalar(@$converted)." files were converted in $minutes minutes $seconds seconds\n";
if (scalar(@$failures) > 0) {
print "\n".scalar(@$failures)." files need a manual fix:\n";
foreach my $failure (@$failures) {
print " $failure\n";
}
}
} elsif (-f $pathname) {
convert_file($pathname);
}
# Converts a directory recursively, selecting only non-version .problem/exam/survey/html/library files.
# Returns a list of files that were converted, and a list of files that could not be converted.
sub convert_dir {
my ($dirpath) = @_;
my @converted = ();
my @failures = ();
opendir (my $dh, $dirpath) or die $!;
while (my $entry = readdir($dh)) {
next if ($entry =~ m/^\./); # ignore entries starting with a period
my $pathname = $dirpath.'/'.$entry;
if (-d $pathname) {
my ($new_converted, $new_failures) = convert_dir($pathname);
push(@converted, @$new_converted);
push(@failures, @$new_failures);
} elsif (-f $pathname) {
# check that the file ends in .problem, .exam, .survey, .html or .htm but not .number.*
if (($pathname =~ /\.problem$/i || $pathname =~ /\.exam$/i || $pathname =~ /\.survey$/i ||
$pathname =~ /\.html?$/i || $pathname =~ /\.library$/i) &&
$pathname !~ /\.[0-9]+\.[a-z]+$/) {
try {
convert_file($pathname);
push(@converted, $pathname);
} catch {
print "$_\n"; # continue processing even if a file cannot be converted
push(@failures, $pathname);
};
}
}
}
closedir($dh);
return((\@converted, \@failures));
}
# Converts a file, creating a .xml file in the same directory.
sub convert_file {
my ($pathname) = @_;
# create a name for the new file
my $newpath = $pathname.'.xml';
print "converting $pathname...\n";
my $textref;
try {
$textref = pre_xml::pre_xml($pathname);
} catch {
die "pre_xml error for $pathname: $_";
};
try {
$textref = html_to_xml::html_to_xml($textref);
} catch {
die "html_to_xml error for $pathname: $_";
};
try {
post_xml::post_xml($textref, $newpath);
} catch {
die "post_xml error for $pathname: $_";
};
}
Index: modules/damieng/clean_xml/html_to_xml.pm
+++ modules/damieng/clean_xml/html_to_xml.pm
#!/usr/bin/perl
package html_to_xml;
use strict;
use utf8;
use warnings;
use HTML::Parser ();
# always closing, end tags are ignored:
my @empty = ('base','br','col','hr','img','input','keygen','link','meta','param','source','track','wbr', 'frame', 'embed','startouttext','endouttext');
#my @block_html = ('html','body','h1','h2','h3','h4','h5','h6','div','p','ul','ol','table','tbody','tr','td','th','dl','pre','noscript','blockquote','object','applet','embed','map','form','fieldset','iframe');
my $result;
my @stack;
my $close_warning;
# This takes non-well-formed UTF-8 LC+HTML and returns well-formed but non-valid XML LC+XHTML.
sub html_to_xml {
my($textref) = @_;
$result = '';
@stack = ();
$close_warning = '';
my $p = HTML::Parser->new( api_version => 3,
start_h => [\&start, "tagname, attr, attrseq"],
end_h => [\&end, "tagname"],
text_h => [\&text, "dtext"],
comment_h => [\&comment, "tokens"],
declaration_h => [\&declaration, "tokens"],
process_h => [\&process, "token0"],
);
# NOTE: by default, the HTML parser turns all attribute and elements names to lowercase
$p->empty_element_tags(1);
$result .= "<?xml version='1.0' encoding='UTF-8'?>\n";
$p->parse($$textref);
for (my $i=scalar(@stack)-1; $i>=0; $i--) {
if ($close_warning ne '') {
$close_warning .= ', ';
}
$close_warning .= $stack[$i];
$result .= '</'.$stack[$i].'>';
}
if ($close_warning ne '') {
print "Warning: the parser had to add closing tags to understand the document ($close_warning)\n";
}
return \$result;
}
sub start {
my($tagname, $attr, $attrseq) = @_;
# NOTE: we could do things more like web browsers, but I'm nore sure the result would be better with LON-CAPA files
# (in problem files there are not so many missing tags)
# See http://www.w3.org/TR/html5/syntax.html#an-introduction-to-error-handling-and-strange-cases-in-the-parser
if ($tagname eq 'o:p') {
return;
}
if ($tagname =~ /@.*\.[a-z]{2,3}$/) { # email <name at hostname>
$result .= "<$tagname>";
return;
}
#$tagname = lc($tagname); this is done by default by the parser
$tagname = fix_tag($tagname);
if (scalar(@stack) > 0 && $stack[scalar(@stack)-1] eq 'tr' && $tagname ne 'tr' && $tagname ne 'td' && $tagname ne 'th' &&
!string_in_array(['part','block','comment','endouttext','problemtype','standalone','startouttext','tex','translated','web','while','randomlist','font','b','form'], $tagname)) {
# NOTE: a 'part' or 'block' element between tr and td will not be valid, but changing tag order would make things worse
# font and b will be removed in post_xml, so we can leave it for now, to handle things like <TR><FONT FACE="Palatino"><TD...
# form is to avoid an empty form in some cases (it might not work anyway, but it is better to keep this bug the way it is)
print "Warning: a <td> tag was added because a $tagname element was directly under a tr\n";
start('td');
}
if ($tagname eq 'p' && scalar(@stack) > 0 && $stack[scalar(@stack)-1] eq 'p') {
end('p');
} elsif ($tagname eq 'li') {
my $ind_li = last_index_of(\@stack, 'li');
my $ind_ul = last_index_of(\@stack, 'ul');
my $ind_ol = last_index_of(\@stack, 'ol');
if ($ind_li != -1 && ($ind_ul == -1 || $ind_ul < $ind_li) && ($ind_ol == -1 || $ind_ol < $ind_li)) {
end('li');
}
} elsif ($tagname eq 'tr') {
my $ind_table = last_index_of(\@stack, 'table');
my $ind_tr = last_index_of(\@stack, 'tr');
if ($ind_tr != -1 && ($ind_table == -1 || $ind_table < $ind_tr)) {
end('tr');
}
} elsif ($tagname eq 'td' || $tagname eq 'th') {
my $ind_table = last_index_of(\@stack, 'table');
my $ind_td = last_index_of(\@stack, 'td');
my $ind_th = last_index_of(\@stack, 'th');
my $ind_tr = last_index_of(\@stack, 'tr');
if ($ind_tr == -1 || ($ind_table != -1 && $ind_table > $ind_tr)) {
start('tr');
$ind_tr = last_index_of(\@stack, 'tr');
}
if ($ind_td != -1 && $ind_tr < $ind_td) {
end('td');
} elsif ($ind_th != -1 && $ind_tr < $ind_th) {
end('th');
}
} elsif ($tagname eq 'dd' || $tagname eq 'dt') {
my $ind_dd = last_index_of(\@stack, 'dd');
my $ind_dt = last_index_of(\@stack, 'dt');
my $ind_dl = last_index_of(\@stack, 'dl');
if ($ind_dl == -1) {
start('dl');
$ind_dl = last_index_of(\@stack, 'dl');
}
if ($ind_dd != -1 && ($ind_dl == -1 || $ind_dl < $ind_dd)) {
end('dd');
} elsif ($ind_dt != -1 && ($ind_dl == -1 || $ind_dl < $ind_dt)) {
end('dt');
}
} elsif ($tagname eq 'option') {
my $ind_option = last_index_of(\@stack, 'option');
if ($ind_option != -1) {
end('option');
}
} elsif ($tagname eq 'area') {
my $ind_area = last_index_of(\@stack, 'area');
if ($ind_area != -1) {
end('area');
}
} elsif ($tagname eq 'a') {
my $ind_a = last_index_of(\@stack, 'a');
if ($ind_a != -1) {
end('a');
}
} elsif ($tagname eq 'num') {
my $ind_num = last_index_of(\@stack, 'num');
if ($ind_num != -1) {
end('num');
}
}
# HTML interpretation of non-closing elements and style is too complex (and error-prone, anyway).
# Since LON-CAPA elements are all supposed to be closed, this interpretation is SGML-like instead.
# Paragraphs inside paragraphs will be fixed later.
# my @styles = ();
# if ($tagname eq 'p') {
# for (my $i=scalar(@stack)-1; $i>=0; $i--) {
# if ($stack[$i] eq 'p') {
# # save the styles
# for (my $j=$i+1; $j<scalar(@stack); $j++) {
# if (index_of(['b','i','em','strong','sub','sup'], $stack[$j]) != -1) {
# push(@styles, $stack[$j]);
# }
# }
# # close the p
# end('p');
# last;
# } elsif (index_of(\@block_html, $stack[$i]) != -1) {
# # stop looking
# last;
# }
# }
# }
$result .= '<'.$tagname;
my %seen = ();
foreach my $att_name (@$attrseq) {
my $att_name_modified = $att_name;
$att_name_modified =~ s/[^\-a-zA-Z0-9_:.]//g;
$att_name_modified =~ s/^[\-.0-9]*//;
if ($att_name_modified ne '' && index($att_name_modified, ':') == -1) {
if ($seen{$att_name_modified}) {
print "Warning: Ignoring duplicate attribute: $att_name\n";
next;
}
$seen{$att_name_modified}++;
my $att_value = $attr->{$att_name};
$att_value =~ s/^[ââ]|[ââ]$//g;
$att_value =~ s/&/&/g;
$att_value =~ s/</</g;
$att_value =~ s/>/>/g;
$att_value =~ s/"/"/g;
if ($tagname eq 'embed' && $att_name_modified eq 'script') {
# newlines are encoded to preserve Protein Explorer scripts in embed script attributes:
$att_value =~ s/\x0A/
/g;
$att_value =~ s/\x0D/
/g;
}
if ($att_name_modified eq 'xmlns' && ($att_value eq 'http://www.w3.org/1999/xhtml' ||
$att_value eq 'http://www.w3.org/TR/REC-html40')) {
next;
}
$result .= ' '.$att_name_modified.'="'.$att_value.'"';
}
}
if (index_of(\@empty, $tagname) != -1) {
$result .= '/>';
} else {
$result .= '>';
push(@stack, $tagname);
if (scalar(@stack) > 500) {
die "This document has a crazy depth - I'm out !";
}
}
# reopen the styles, if any
#for (my $j=0; $j<scalar(@styles); $j++) {
# start($styles[$j], {}, ());
#}
}
sub end {
my($tagname) = @_;
if ($tagname eq 'o:p') {
return;
}
$tagname = fix_tag($tagname);
if (index_of(\@empty, $tagname) != -1) {
return;
}
if ($tagname eq 'td' && scalar(@stack) > 0 && $stack[scalar(@stack)-1] eq 'th') {
# handle <th>text</td> as if it was <th>text</th>
$tagname = 'th';
} elsif ($tagname eq 'th' && scalar(@stack) > 0 && $stack[scalar(@stack)-1] eq 'td') {
# handle <td>text</th> as if it was <td>text</td>
$tagname = 'td';
}
my $found = 0;
for (my $i=scalar(@stack)-1; $i>=0; $i--) {
if ($stack[$i] eq $tagname) {
for (my $j=scalar(@stack)-1; $j>$i; $j--) {
if ($close_warning ne '') {
$close_warning .= ', ';
}
$close_warning .= $stack[$j];
$result .= '</'.$stack[$j].'>';
}
splice(@stack, $i, scalar(@stack)-$i);
$found = 1;
last;
} elsif (index_of(\@stack, 'web') != -1) {
die "There is a web element with missing end tags inside - it has to be fixed by hand";
}
}
if ($found) {
$result .= '</'.$tagname.'>';
} elsif ($tagname eq 'p') {
$result .= '<p/>';
}
}
sub text {
my($dtext) = @_;
$dtext =~ s/&/&/g;
$dtext =~ s/</</g;
$dtext =~ s/>/>/g;
$dtext =~ s/"/"/g;
$result .= $dtext;
}
sub comment {
my($tokens) = @_;
# NOTE: the HTML parser thinks this is a comment: </ br>
# and LON-CAPA has sometimes turned that into <![CDATA[</ br>]]>
foreach my $comment (@$tokens) {
$comment =~ s/--/- /g;
$comment =~ s/^-|-$/ /g;
$result .= '<!--'.$comment.'-->';
}
}
sub declaration {
my($tokens) = @_;
# ignore them
#$result .= '<!';
#$result .= join(' ', @$tokens);
#$result .= '>';
}
sub process {
my($token0) = @_;
if ($token0 ne '') {
$result .= '<?'.$token0.'>';
}
}
sub index_of {
my ($array, $value) = @_;
for (my $i=0; $i<scalar(@{$array}); $i++) {
if ($array->[$i] eq $value) {
return $i;
}
}
return -1;
}
sub last_index_of {
my ($array, $value) = @_;
for (my $i=scalar(@{$array})-1; $i>=0; $i--) {
if ($array->[$i] eq $value) {
return $i;
}
}
return -1;
}
sub fix_tag {
my ($tag) = @_;
#$tag = lc($tag); this is done by default by the parser
if ($tag !~ /^[a-zA-Z_][a-zA-Z0-9_\-\.]*$/) {
print "Warning: bad start tag:'".$tag."'";
if ($tag =~ /<[a-zA-Z]/) {
$tag =~ s/^[^<]*<//; # a<b -> b
}
if ($tag =~ /[a-zA-Z]=/) {
$tag =~ s/=.*$//; # a=b -> a
}
if ($tag =~ /[a-zA-Z]\//) {
$tag =~ s/\/.*$//; # a/b -> a
}
if ($tag =~ /:/) {
# a:b -> b except when : at the end
if ($tag =~ /^[^:]*:$/) {
$tag =~ s/://;
} else {
$tag =~ s/^.*://;
}
}
$tag =~ s/^[0-9\-\.]+//;
$tag =~ s/[^a-zA-Z0-9_\-\.]//g;
print " (converted to $tag)\n";
}
return($tag);
}
##
# Tests if a string is in an array (using eq) (to avoid Smartmatch warnings with $value ~~ @array)
# @param {Array<string>} array - reference to the array of strings
# @param {string} value - the string to look for
# @returns 1 if found, 0 otherwise
##
sub string_in_array {
my ($array, $value) = @_;
foreach my $v (@{$array}) {
if ($v eq $value) {
return 1;
}
}
return 0;
}
1;
__END__
Index: modules/damieng/clean_xml/loncapa.xsd
+++ modules/damieng/clean_xml/loncapa.xsd
<?xml version="1.0" encoding="UTF-8"?><xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xml:lang="en">
<xs:annotation>
<xs:documentation>
XML schema for LON-CAPA 2 documents.
</xs:documentation>
</xs:annotation>
<xs:import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="xml.xsd">
<xs:annotation>
<xs:documentation>
This import is needed to use xml:space="preserve".
</xs:documentation>
</xs:annotation>
</xs:import>
<xs:annotation>
<xs:documentation>
Shared simple types.
</xs:documentation>
</xs:annotation>
<xs:simpleType name="perl">
<xs:restriction base="xs:string">
<xs:pattern value="\s*-?(($|&)([#|$]*[A-Za-z][\w_]*|\{[A-Za-z][\w_]*\}))([\[\{].+[\]\}])*(\([^$&\)]+\))*\s*"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="int-or-perl">
<xs:union memberTypes="xs:int perl"/>
</xs:simpleType>
<xs:simpleType name="non-negative-int-or-perl">
<xs:union memberTypes="xs:nonNegativeInteger perl"/>
</xs:simpleType>
<xs:simpleType name="decimal-or-perl">
<xs:union memberTypes="xs:decimal perl"/>
</xs:simpleType>
<xs:simpleType name="real">
<xs:restriction base="xs:string">
<xs:pattern value="[+-]?\d*\.?\d*([eE][+-]?\d+)?"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="real-or-perl">
<xs:union memberTypes="real perl"/>
</xs:simpleType>
<xs:simpleType name="yesno">
<xs:restriction base="xs:string">
<xs:enumeration value="yes"/>
<xs:enumeration value="no"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="yesno-or-perl">
<xs:union memberTypes="yesno perl"/>
</xs:simpleType>
<xs:simpleType name="onoff">
<xs:restriction base="xs:string">
<xs:enumeration value="on"/>
<xs:enumeration value="off"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="onoff-or-perl">
<xs:union memberTypes="onoff perl"/>
</xs:simpleType>
<xs:simpleType name="color">
<xs:restriction base="xs:string">
<xs:pattern value="[x#][\da-fA-F]{6}"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="color-or-perl">
<xs:union memberTypes="color perl"/>
</xs:simpleType>
<xs:simpleType name="location">
<xs:restriction base="xs:string">
<xs:enumeration value="random"/>
<xs:enumeration value="top"/>
<xs:enumeration value="bottom"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="location-or-perl">
<xs:union memberTypes="location perl"/>
</xs:simpleType>
<xs:annotation>
<xs:documentation>
Shared attributes
</xs:documentation>
</xs:annotation>
<xs:attribute default="normalsize" name="TeXsize">
<xs:annotation>
<xs:documentation>
Size of LaTeX fonts used in printing.
Possible values of TeXsize attribute:
- tiny: smallest
- scriptsize: very small
- footnotesize: smaller
- small: small
- normalsize: normal
- large: large
- Large: larger
- LARGE: even larger
- huge: still larger
- Huge: largest
Note, that all parameters coincide with standard LaTeX commands for changing font size though you do not escape them.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="tiny"/>
<xs:enumeration value="scriptsize"/>
<xs:enumeration value="footnotesize"/>
<xs:enumeration value="small"/>
<xs:enumeration value="normalsize"/>
<xs:enumeration value="large"/>
<xs:enumeration value="Large"/>
<xs:enumeration value="LARGE"/>
<xs:enumeration value="huge"/>
<xs:enumeration value="Huge"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:annotation>
<xs:documentation>
Problem (root element)
</xs:documentation>
</xs:annotation>
<xs:element name="problem">
<xs:annotation>
<xs:documentation>
Root for .problem documents.
This must be first in the file. It sets up the header of the webpage and generates the submit buttons. It also handles due dates properly.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-with-parts"/>
<xs:group ref="inserts"/>
<xs:element ref="allow"/>
<xs:element ref="meta"/>
<xs:element ref="parameter"/>
<xs:element ref="parserlib"/>
<xs:element ref="scriptlib"/>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:annotation>
<xs:documentation>
Library (root element)
</xs:documentation>
</xs:annotation>
<xs:element name="library">
<xs:annotation>
<xs:documentation>
Root for .library documents.
A LON-CAPA .library file can contain just a script block, or just response items, or both.
Library content is loaded into a problem statement by using an <import> tag.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-with-responses"/>
<xs:group ref="inserts"/>
<xs:element ref="part"/>
<xs:element ref="parserlib"/>
<xs:element ref="scriptlib"/>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:annotation>
<xs:documentation>
Groups of block and inline elements.
</xs:documentation>
</xs:annotation>
<xs:group name="text-with-parts">
<xs:annotation>
<xs:documentation>
List of block and inline elements mixed with text. These elements can contain parts and responses.
</xs:documentation>
</xs:annotation>
<xs:choice>
<xs:element ref="part"/>
<xs:group ref="responses"/>
<xs:group ref="blocks-with-parts"/>
<xs:group ref="inlines"/>
</xs:choice>
</xs:group>
<xs:group name="text-with-responses">
<xs:annotation>
<xs:documentation>
List of block and inline elements mixed with text. These elements can contain responses but not parts.
</xs:documentation>
</xs:annotation>
<xs:choice>
<xs:group ref="responses"/>
<xs:group ref="blocks-with-responses"/>
<xs:group ref="inlines"/>
</xs:choice>
</xs:group>
<xs:group name="text-only">
<xs:annotation>
<xs:documentation>
List of block and inline elements mixed with text. These elements cannot contain responses or parts.
</xs:documentation>
</xs:annotation>
<xs:choice>
<xs:group ref="blocks-with-text"/>
<xs:group ref="inlines"/>
</xs:choice>
</xs:group>
<xs:group name="universal-blocks">
<xs:annotation>
<xs:documentation>
Blocks with a content that does not depend on the scope, and that can appear anywhere with text and blocks.
</xs:documentation>
</xs:annotation>
<xs:choice>
<xs:element ref="randomlabel"/>
<xs:element ref="import"/>
<xs:element ref="while"/>
<xs:element ref="tex"/>
<xs:element ref="print"/>
<xs:element ref="web"/>
<xs:element ref="standalone"/>
<xs:element ref="script"/>
<xs:element ref="languageblock"/>
<xs:element ref="translated"/>
<xs:element ref="window"/>
<xs:element ref="windowlink"/>
<xs:element ref="togglebox"/>
<xs:element ref="instructorcomment"/>
<xs:element ref="comment"/>
<xs:element ref="gnuplot"/>
<xs:element ref="organicstructure"/>
<xs:element ref="drawimage"/>
<xs:element ref="solved"/>
<xs:element ref="notsolved"/>
<xs:group ref="heading"/>
<xs:element ref="noscript"/>
<xs:element ref="header"/>
<xs:element ref="footer"/>
<xs:element ref="aside"/>
<xs:element ref="pre"/>
<xs:element ref="hr"/>
<xs:element ref="address"/>
<xs:element ref="blockquote"/>
<xs:element ref="figure"/>
<xs:element ref="object"/>
<xs:element ref="applet"/>
<xs:element ref="embed"/>
<xs:element ref="video"/>
<xs:element ref="audio"/>
<xs:element ref="map"/>
<xs:element ref="canvas"/>
<xs:element ref="form"/>
<xs:element ref="fieldset"/>
<xs:element ref="iframe"/>
</xs:choice>
</xs:group>
<xs:group name="blocks-with-parts">
<xs:choice>
<xs:group ref="universal-blocks"/>
<xs:element name="block" type="block-with-parts"/>
<xs:element name="problemtype" type="problemtype-with-parts"/>
<xs:element name="randomlist" type="randomlist-with-parts"/>
<xs:element name="section" type="section-with-parts"/>
<xs:element name="div" type="div-with-parts"/>
<xs:element name="p" type="p-with-responses"/>
<xs:element name="ul" type="ul-with-parts"/>
<xs:element name="ol" type="ol-with-parts"/>
<xs:element name="dl" type="dl-with-parts"/>
<xs:element name="table" type="table-with-parts"/>
</xs:choice>
</xs:group>
<xs:group name="blocks-with-responses">
<xs:choice>
<xs:group ref="universal-blocks"/>
<xs:element name="block" type="block-with-responses"/>
<xs:element name="problemtype" type="problemtype-with-responses"/>
<xs:element name="randomlist" type="randomlist-with-responses"/>
<xs:element name="section" type="section-with-responses"/>
<xs:element name="div" type="div-with-responses"/>
<xs:element name="p" type="p-with-responses"/>
<xs:element name="ul" type="ul-with-responses"/>
<xs:element name="ol" type="ol-with-responses"/>
<xs:element name="dl" type="dl-with-responses"/>
<xs:element name="table" type="table-with-responses"/>
</xs:choice>
</xs:group>
<xs:group name="blocks-with-text">
<xs:choice>
<xs:group ref="universal-blocks"/>
<xs:element name="block" type="block-with-text"/>
<xs:element name="problemtype" type="problemtype-with-text"/>
<xs:element name="randomlist" type="randomlist-with-text"/>
<xs:element name="section" type="section-with-text"/>
<xs:element name="div" type="div-with-text"/>
<xs:element name="p" type="p-with-text"/>
<xs:element name="ul" type="ul-with-text"/>
<xs:element name="ol" type="ol-with-text"/>
<xs:element name="dl" type="dl-with-text"/>
<xs:element name="table" type="table-with-text"/>
</xs:choice>
</xs:group>
<xs:group name="inlines">
<xs:choice>
<xs:element ref="display"/>
<xs:element ref="m"/>
<xs:element ref="lm"/>
<xs:element ref="chem"/>
<xs:element ref="num"/>
<xs:element ref="parse"/>
<xs:element ref="algebra"/>
<xs:element ref="textline"/>
<xs:element ref="displayweight"/>
<xs:element ref="displaystudentphoto"/>
<xs:element ref="span"/>
<xs:element ref="a"/>
<xs:element ref="strong"/>
<xs:element ref="em"/>
<xs:element ref="b"/>
<xs:element ref="i"/>
<xs:element ref="sup"/>
<xs:element ref="sub"/>
<xs:element ref="code"/>
<xs:element ref="kbd"/>
<xs:element ref="samp"/>
<xs:element ref="cite"/>
<xs:element ref="q"/>
<xs:element ref="tt"/>
<xs:element ref="ins"/>
<xs:element ref="del"/>
<xs:element ref="var"/>
<xs:element ref="small"/>
<xs:element ref="big"/>
<xs:element ref="br"/>
<xs:element ref="img"/>
<xs:element ref="input"/>
<xs:element ref="select"/>
<xs:element ref="textarea"/>
<xs:element ref="label"/>
<xs:element ref="button"/>
</xs:choice>
</xs:group>
<xs:group name="inserts">
<xs:annotation>
<xs:documentation>
List of elements that insert something if a condition is verified.
</xs:documentation>
</xs:annotation>
<xs:choice>
<xs:element ref="displaytitle"/>
<xs:element ref="displayduedate"/>
<xs:element ref="preduedate"/>
<xs:element ref="postanswerdate"/>
</xs:choice>
</xs:group>
<xs:annotation>
<xs:documentation>
List of responses
</xs:documentation>
</xs:annotation>
<xs:group name="responses">
<xs:choice>
<xs:group ref="inlineResponses"/>
<xs:group ref="blockResponses"/>
</xs:choice>
</xs:group>
<xs:group name="inlineResponses">
<xs:choice>
<xs:element ref="stringresponse"/>
<xs:element ref="optionresponse"/>
<xs:element ref="numericalresponse"/>
<xs:element ref="formularesponse"/>
<xs:element ref="mathresponse"/>
<xs:element ref="organicresponse"/>
<xs:element ref="reactionresponse"/>
<xs:element ref="customresponse"/>
<xs:element ref="externalresponse"/>
</xs:choice>
</xs:group>
<xs:group name="blockResponses">
<xs:choice>
<xs:element ref="essayresponse"/>
<xs:element ref="radiobuttonresponse"/>
<xs:element ref="matchresponse"/>
<xs:element ref="rankresponse"/>
<xs:element ref="imageresponse"/>
<xs:element ref="functionplotresponse"/>
<xs:element ref="dataresponse"/>
</xs:choice>
</xs:group>
<xs:attributeGroup name="response-identification">
<xs:attribute name="id" type="xs:string">
<xs:annotation>
<xs:documentation>
Unique identifier for the response in the document. If this isnât set, it will be set during the publication step. It is used to assign parameter names in a way that can be tracked if an instructor modifies by hand.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="name" type="xs:string">
<xs:annotation>
<xs:documentation>
If set, the name will be used by the resource assembly tool when one is modifying parameters.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:annotation>
<xs:documentation>
String response
</xs:documentation>
</xs:annotation>
<xs:element name="stringresponse">
<xs:annotation>
<xs:documentation>
Query for a string.
An internal textline tag is necessary for the studentâs response to go in. It can check the string for either case or order.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="responseparam"/>
<xs:element ref="hiddenline"/>
<xs:element ref="hintgroup"/>
<xs:element ref="stringhint"/>
<xs:element ref="hintpart"/>
<xs:group ref="text-only"/>
</xs:choice>
<xs:attributeGroup ref="response-identification"/>
<xs:attribute name="answer" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>
the correct answer, either a perl list or scalar
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="cs" name="type">
<xs:annotation>
<xs:documentation>
Specifies how the string is checked (like the CAPA styles). Possible values are:
â cs: case sensitive, order important.
â ci: case insensitive, order important.
â mc: case insensitive, order unimportant.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="cs"/>
<xs:enumeration value="ci"/>
<xs:enumeration value="mc"/>
<xs:enumeration value="re"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="answerdisplay" type="xs:string">
<xs:annotation>
<xs:documentation>
String to display for answer
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="preprocess" type="xs:string">
<xs:annotation>
<xs:documentation>
Pre-Processor Subroutine
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:annotation>
<xs:documentation>
Essay response
</xs:documentation>
</xs:annotation>
<xs:element name="essayresponse">
<xs:annotation>
<xs:documentation>
Query for a long text or a line, possibly with spell checking.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="responseparam"/>
<xs:element ref="textfield"/>
<xs:element ref="hiddensubmission"/>
<xs:element ref="hiddenline"/>
<xs:element ref="hintgroup"/>
<xs:element ref="stringhint"/>
<xs:element ref="hintpart"/>
<xs:group ref="text-only"/>
</xs:choice>
<xs:attributeGroup ref="response-identification"/>
</xs:complexType>
</xs:element>
<xs:annotation>
<xs:documentation>
Radio button response
</xs:documentation>
</xs:annotation>
<xs:element name="radiobuttonresponse">
<xs:annotation>
<xs:documentation>
Query for a single choice among several statements.
The value of the foils can only be true, false, or unused.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="responseparam"/>
<xs:element name="foilgroup" type="radiobuttonresponse--foilgroup"/>
<xs:element ref="hintgroup"/>
<xs:element ref="radiobuttonhint"/>
<xs:element ref="hintpart"/>
<xs:group ref="text-only"/>
</xs:choice>
<xs:attributeGroup ref="response-identification"/>
<xs:attribute name="max" type="int-or-perl">
<xs:annotation>
<xs:documentation>
Max Number Of Shown Foils
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="yes" name="randomize" type="yesno-or-perl">
<xs:annotation>
<xs:documentation>
Randomize Foil Order
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="vertical" name="direction">
<xs:annotation>
<xs:documentation>
Display Direction
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="vertical"/>
<xs:enumeration value="horizontal"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:complexType name="radiobuttonresponse--foilgroup">
<xs:annotation>
<xs:documentation>Collection of Foils</xs:documentation>
</xs:annotation>
<xs:choice maxOccurs="unbounded">
<xs:element name="conceptgroup" type="radiobuttonresponse--conceptgroup"/>
<xs:element name="foil" type="radiobuttonresponse--foil"/>
<xs:element ref="comment"/>
</xs:choice>
</xs:complexType>
<xs:complexType name="radiobuttonresponse--conceptgroup">
<xs:annotation>
<xs:documentation>
Collection of similar foils.
When a problem is displayed, only one of the contained foils is selected for display.
</xs:documentation>
</xs:annotation>
<xs:choice maxOccurs="unbounded">
<xs:element name="foil" type="radiobuttonresponse--foil"/>
</xs:choice>
<xs:attribute name="concept" type="xs:string" use="required"/>
</xs:complexType>
<xs:complexType mixed="true" name="radiobuttonresponse--foil">
<xs:annotation>
<xs:documentation>
Statement next to the radio button.
</xs:documentation>
</xs:annotation>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
<xs:element ref="parserlib"/>
<xs:element ref="scriptlib"/>
</xs:choice>
<xs:attribute name="name" type="xs:string" use="required"/>
<xs:attribute name="value" use="required">
<xs:annotation>
<xs:documentation>
Correct Option (true, false, or unused).
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="true"/>
<xs:enumeration value="false"/>
<xs:enumeration value="unused"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute default="random" name="location" type="location-or-perl"/>
</xs:complexType>
<xs:annotation>
<xs:documentation>
Option response
</xs:documentation>
</xs:annotation>
<xs:element name="optionresponse">
<xs:annotation>
<xs:documentation>
Query for a choice for each given statement.
Option Response problems present foils to the student with drop-down boxes. The student can select the matching choice for the foils from a list of choices. Optionally, the foils may be bundled into Concept Groups and the system will select one foil from each group to display to the student.
By default, the list of options is presented in front of the foils. Using the optional drawoptionlist element, the list of options can be embedded into the foil.
The foilgroup is required to have an options attribute which should be a perl list of possible options for the student.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="responseparam"/>
<xs:element name="foilgroup" type="optionresponse--foilgroup"/>
<xs:element ref="hintgroup"/>
<xs:element ref="optionhint"/>
<xs:element ref="hintpart"/>
<xs:group ref="text-only"/>
</xs:choice>
<xs:attributeGroup ref="response-identification"/>
<xs:attribute name="max" type="int-or-perl">
<xs:annotation>
<xs:documentation>
Max Number Of Shown Foils
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="yes" name="randomize" type="yesno-or-perl">
<xs:annotation>
<xs:documentation>
Randomize Foil Order
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="horizontal" name="TeXlayout">
<xs:annotation>
<xs:documentation>
Display of options when printed
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="horizontal"/>
<xs:enumeration value="vertical"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:complexType name="optionresponse--foilgroup">
<xs:annotation>
<xs:documentation>Collection of Foils</xs:documentation>
</xs:annotation>
<xs:choice maxOccurs="unbounded">
<xs:element name="conceptgroup" type="optionresponse--conceptgroup"/>
<xs:element name="foil" type="optionresponse--foil"/>
<xs:element ref="comment"/>
</xs:choice>
<xs:attribute name="options" use="required">
<xs:annotation>
<xs:documentation>
Perl list of possible foil values.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="@[a-zA-Z0-9_\-]+"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="\(.+\)"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="texoptions">
<xs:annotation>
<xs:documentation>
Print options
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="nochoice"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="checkboxvalue" type="xs:string">
<xs:annotation>
<xs:documentation>
Two-option checkboxes for
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="checkboxoptions">
<xs:annotation>
<xs:documentation>
Checkbox options
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="nochoice"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
<xs:complexType name="optionresponse--conceptgroup">
<xs:annotation>
<xs:documentation>
Collection of similar foils.
When a problem is displayed, only one of the contained foils is selected for display.
</xs:documentation>
</xs:annotation>
<xs:choice maxOccurs="unbounded">
<xs:element name="foil" type="optionresponse--foil"/>
</xs:choice>
<xs:attribute name="concept" type="xs:string" use="required"/>
</xs:complexType>
<xs:complexType mixed="true" name="optionresponse--foil">
<xs:annotation>
<xs:documentation>
Statement next to the drop-down box.
</xs:documentation>
</xs:annotation>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
<xs:element ref="parserlib"/>
<xs:element ref="scriptlib"/>
<xs:element name="drawoptionlist" type="optionresponse--drawoptionlist"/>
</xs:choice>
<xs:attribute name="name" type="xs:string" use="required"/>
<xs:attribute name="value" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>
Correct Option
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="random" name="location" type="location-or-perl"/>
</xs:complexType>
<xs:complexType name="optionresponse--drawoptionlist">
<xs:annotation>
<xs:documentation>Draw Option List</xs:documentation>
</xs:annotation>
</xs:complexType>
<xs:annotation>
<xs:documentation>
Match response
</xs:documentation>
</xs:annotation>
<xs:element name="matchresponse">
<xs:annotation>
<xs:documentation>
Query for matches betweens items from two lists.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="responseparam"/>
<xs:element name="foilgroup" type="matchresponse--foilgroup"/>
<xs:element ref="hintgroup"/>
<xs:group ref="text-only"/>
</xs:choice>
<xs:attributeGroup ref="response-identification"/>
<xs:attribute name="max" type="int-or-perl">
<xs:annotation>
<xs:documentation>
Max Number Of Shown Foils
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="yes" name="randomize" type="yesno-or-perl">
<xs:annotation>
<xs:documentation>
Randomize Foil Order
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:complexType name="matchresponse--itemgroup">
<xs:annotation>
<xs:documentation>Items to Match</xs:documentation>
</xs:annotation>
<xs:choice maxOccurs="unbounded">
<xs:element name="item" type="matchresponse--item"/>
</xs:choice>
<xs:attribute default="yes" name="randomize" type="yesno-or-perl">
<xs:annotation>
<xs:documentation>
Randomize Order
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="top" name="location">
<xs:annotation>
<xs:documentation>
Items Display Location
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="top"/>
<xs:enumeration value="bottom"/>
<xs:enumeration value="left"/>
<xs:enumeration value="right"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute default="vertical" name="direction">
<xs:annotation>
<xs:documentation>
Items Display Direction
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="vertical"/>
<xs:enumeration value="horizontal"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="columns">
<xs:annotation>
<xs:documentation>
Items Columns
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:int">
<xs:enumeration value="1"/>
<xs:enumeration value="2"/>
<xs:enumeration value="3"/>
<xs:enumeration value="4"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="TeXitemgroupwidth">
<xs:annotation>
<xs:documentation>
TeXitemgroupwidth attribute allows you to specify the width of table with items for matching. The value of this attribute defines the width in percents with respect to text line width.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="\d+%"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
<xs:complexType mixed="true" name="matchresponse--item">
<xs:annotation>
<xs:documentation>Item</xs:documentation>
</xs:annotation>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
<xs:element ref="parserlib"/>
<xs:element ref="scriptlib"/>
</xs:choice>
<xs:attribute name="name" type="xs:string"/>
<xs:attribute default="random" name="location" type="location-or-perl"/>
</xs:complexType>
<xs:complexType name="matchresponse--foilgroup">
<xs:annotation>
<xs:documentation>Collection of Foils</xs:documentation>
</xs:annotation>
<xs:choice maxOccurs="unbounded">
<xs:element name="itemgroup" type="matchresponse--itemgroup"/>
<xs:element name="conceptgroup" type="matchresponse--conceptgroup"/>
<xs:element name="foil" type="matchresponse--foil"/>
<xs:element ref="comment"/>
</xs:choice>
</xs:complexType>
<xs:complexType name="matchresponse--conceptgroup">
<xs:annotation>
<xs:documentation>
Collection of similar foils.
When a problem is displayed, only one of the contained foils is selected for display.
</xs:documentation>
</xs:annotation>
<xs:choice maxOccurs="unbounded">
<xs:element name="foil" type="matchresponse--foil"/>
</xs:choice>
<xs:attribute name="concept" type="xs:string" use="required"/>
</xs:complexType>
<xs:complexType mixed="true" name="matchresponse--foil">
<xs:annotation>
<xs:documentation>Foil</xs:documentation>
</xs:annotation>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
<xs:element ref="parserlib"/>
<xs:element ref="scriptlib"/>
</xs:choice>
<xs:attribute name="name" type="xs:string" use="required"/>
<xs:attribute name="value" type="xs:string">
<xs:annotation>
<xs:documentation>
Correct Option
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="random" name="location" type="location-or-perl"/>
</xs:complexType>
<xs:annotation>
<xs:documentation>
Rank response
</xs:documentation>
</xs:annotation>
<xs:element name="rankresponse">
<xs:annotation>
<xs:documentation>
Query to sort a list of items in the right order.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="responseparam"/>
<xs:element name="foilgroup" type="rankresponse--foilgroup"/>
<xs:element ref="hintgroup"/>
<xs:group ref="text-only"/>
</xs:choice>
<xs:attributeGroup ref="response-identification"/>
<xs:attribute name="max" type="int-or-perl">
<xs:annotation>
<xs:documentation>
Max Number Of Shown Foils
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="yes" name="randomize" type="yesno-or-perl">
<xs:annotation>
<xs:documentation>
Randomize Foil Order
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:complexType name="rankresponse--foilgroup">
<xs:annotation>
<xs:documentation>Collection of Foils</xs:documentation>
</xs:annotation>
<xs:choice maxOccurs="unbounded">
<xs:element name="conceptgroup" type="rankresponse--conceptgroup"/>
<xs:element name="foil" type="rankresponse--foil"/>
<xs:element ref="comment"/>
</xs:choice>
</xs:complexType>
<xs:complexType name="rankresponse--conceptgroup">
<xs:annotation>
<xs:documentation>
Collection of similar foils.
When a problem is displayed, only one of the contained foils is selected for display.
</xs:documentation>
</xs:annotation>
<xs:choice maxOccurs="unbounded">
<xs:element name="foil" type="rankresponse--foil"/>
</xs:choice>
<xs:attribute name="concept" type="xs:string" use="required"/>
</xs:complexType>
<xs:complexType mixed="true" name="rankresponse--foil">
<xs:annotation>
<xs:documentation>Foil</xs:documentation>
</xs:annotation>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
<xs:element ref="parserlib"/>
<xs:element ref="scriptlib"/>
</xs:choice>
<xs:attribute name="name" type="xs:string" use="required"/>
<xs:attribute name="value" type="xs:string">
<xs:annotation>
<xs:documentation>
Rank Value
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="random" name="location" type="location-or-perl"/>
</xs:complexType>
<xs:annotation>
<xs:documentation>
Image response
</xs:documentation>
</xs:annotation>
<xs:element name="imageresponse">
<xs:annotation>
<xs:documentation>
Query for the selection of an image in a list.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="responseparam"/>
<xs:element name="foilgroup" type="imageresponse--foilgroup"/>
<xs:element ref="hintgroup"/>
<xs:group ref="text-only"/>
</xs:choice>
<xs:attributeGroup ref="response-identification"/>
<xs:attribute name="max" type="int-or-perl">
<xs:annotation>
<xs:documentation>
Max Number Of Shown Foils
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:complexType name="imageresponse--foilgroup">
<xs:annotation>
<xs:documentation>Collection of Imageresponse foils</xs:documentation>
</xs:annotation>
<xs:choice maxOccurs="unbounded">
<xs:element name="conceptgroup" type="imageresponse--conceptgroup"/>
<xs:element name="foil" type="imageresponse--foil"/>
<xs:element ref="comment"/>
</xs:choice>
</xs:complexType>
<xs:complexType name="imageresponse--conceptgroup">
<xs:annotation>
<xs:documentation>
Collection of similar foils.
When a problem is displayed, only one of the contained foils is selected for display.
</xs:documentation>
</xs:annotation>
<xs:choice maxOccurs="unbounded">
<xs:element name="foil" type="imageresponse--foil"/>
</xs:choice>
<xs:attribute name="concept" type="xs:string" use="required"/>
</xs:complexType>
<xs:complexType name="imageresponse--foil">
<xs:annotation>
<xs:documentation>Image response foil. image and rectangle are required.</xs:documentation>
</xs:annotation>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element name="image" type="imageresponse--image"/>
<xs:element name="polygon" type="imageresponse--polygon"/>
<xs:element name="rectangle" type="imageresponse--rectangle"/>
<xs:element name="text" type="imageresponse--text"/>
</xs:choice>
<xs:attribute name="name" type="xs:string" use="required"/>
</xs:complexType>
<xs:complexType mixed="true" name="imageresponse--image">
<xs:annotation>
<xs:documentation>
Imageresponse Image (contains the image source file).
The delimited text should correspond to a published image resource.
Example: <image>/res/adm/includes/templates/man1.jpg</image>. The following image formats are recommended - gif, jpg or png. Other formats may work, but there may be printing or display issues. The image should only appear once per foil.
</xs:documentation>
</xs:annotation>
<xs:choice minOccurs="0">
<xs:element ref="gnuplot"/>
</xs:choice>
</xs:complexType>
<xs:simpleType name="imageresponse--rectangle">
<xs:annotation>
<xs:documentation>
Rectangular area in image (contains coordinate pairs).
The delimited text specifies a rectangular area that is correct, specified as (x1,y1)-(x2,y2), where x1, x2, y1, and y2 are number corresponding to the x and y coordinates of two corners that define a rectangle which specifies where the right answer for this foil is located on the image. For example, (0,0)-(100,200) will specify that a rectangle 100 pixels wide and 200 pixels tall, situated in the upper left of the image, is correct. At least one rectangle is required; multiple rectangles may be specified.
</xs:documentation>
</xs:annotation>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="\s*\(.+\)-\(.+\)\s*"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
<xs:simpleType name="imageresponse--polygon">
<xs:annotation>
<xs:documentation>Polygonal area in image (contains coordinate list)</xs:documentation>
</xs:annotation>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="\s*\(.+\)(-\(.+\))+\s*"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
<xs:complexType mixed="true" name="imageresponse--text">
<xs:annotation>
<xs:documentation>
Text to describe option
The delimited text is printed before the image is shown on the screen.
This text is typically used to describe to the student what they are expected to click on.
</xs:documentation>
</xs:annotation>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
<xs:element ref="parserlib"/>
<xs:element ref="scriptlib"/>
</xs:choice>
</xs:complexType>
<xs:annotation>
<xs:documentation>
Numerical response
</xs:documentation>
</xs:annotation>
<xs:element name="numericalresponse">
<xs:annotation>
<xs:documentation>
Query for one or several numbers, possibly with units.
A tolerance parameter should be used to determine how closely the system will require the studentâs answer to be in order to count it correct. The tolerance will default to zero if it is not defined. The tolerance parameter should always be defined for a numerical problem unless you are certain only integer answers are generated from your script and you want students to reply with exactly that integer.
A significant figures parameter tells the system how many significant figures there are in the problem, as either a single number, e.g. 3, or a range of acceptable values, expressed as min,max. The system will check to make sure that the studentâs answer contains this many significant digits, useful in many scientific calculations.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="responseparam"/>
<xs:element name="answergroup" type="caparesponse--answergroup"/>
<xs:element ref="hiddenline"/>
<xs:element ref="hintgroup"/>
<xs:element ref="numericalhint"/>
<xs:element ref="hintpart"/>
<xs:group ref="text-only"/>
</xs:choice>
<xs:attributeGroup ref="response-identification"/>
<xs:attribute name="answer" type="xs:string">
<xs:annotation>
<xs:documentation>
The answer the system is looking for. The answer can use variables calculated/defined in the problemâs script block, allowing the answer to be determined dynamically (including randomization).
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="incorrect" type="xs:string">
<xs:annotation>
<xs:documentation>
Incorrect Answers
When switched into exam ("bubble sheet") mode, LON-CAPA usually create wrong answers automatically. To specify wrong answers yourself, you need to provide an array of incorrect values in this attribute. You need to provide at least as many incorrects as 1 less than the number of bubbles on the exam. You can provide more if you want to.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="unit" type="xs:string">
<xs:annotation>
<xs:documentation>
Expected units for the answer. For instance, "m/s^2" or "km/(A*hr)".
LON-CAPA will automatically perform some conversions between units of the same dimension when units are provided for a problem. You can provide an answer of "1.45 km" for a distance. If the computer expects the answer in cm, it will convert your answer before comparing against the numerical solution.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="format" type="xs:string">
<xs:annotation>
<xs:documentation>
You can format the number displayed by the computer as the answer. For instance, if the answer is one-third, the computer will display that it computed ".333333333" as the answer. If you'd like to shorten that, you can use the Format field. Format strings like "2E" (without the quotes) will display three significant digits in scientific notation. Format strings like "2f" will display two digits after the decimal point. Format strings like "2s" will round a number to 2 significant digits.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="preprocess" type="xs:string">
<xs:annotation>
<xs:documentation>
Pre-Processor Subroutine
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:complexType name="caparesponse--answergroup">
<xs:annotation>
<xs:documentation>Collection of Answers</xs:documentation>
</xs:annotation>
<xs:choice maxOccurs="unbounded">
<xs:element name="answer" type="caparesponse--answer"/>
</xs:choice>
<xs:attribute default="ordered" name="type">
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="ordered"/>
<xs:enumeration value="unordered"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
<xs:complexType name="caparesponse--answer">
<xs:annotation>
<xs:documentation>
Correct list of values or vectors.
</xs:documentation>
</xs:annotation>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element name="vector" type="caparesponse--vector"/>
<xs:element name="value" type="caparesponse--value"/>
</xs:choice>
<xs:attribute name="name" type="xs:string"/>
<xs:attribute default="ordered" name="type">
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="ordered"/>
<xs:enumeration value="unordered"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
<xs:simpleType name="caparesponse--value">
<xs:annotation>
<xs:documentation>Value</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:string"/>
</xs:simpleType>
<xs:simpleType name="caparesponse--vector">
<xs:annotation>
<xs:documentation>Vector</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:string"/>
</xs:simpleType>
<xs:annotation>
<xs:documentation>
Formula response (using caparesponse--answergroup like numericalresponse).
</xs:documentation>
</xs:annotation>
<xs:element name="formularesponse">
<xs:annotation>
<xs:documentation>Query for a formula.</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="responseparam"/>
<xs:element name="answergroup" type="caparesponse--answergroup"/>
<xs:element ref="hiddenline"/>
<xs:element ref="hintgroup"/>
<xs:element ref="formulahint"/>
<xs:element ref="hintpart"/>
<xs:group ref="text-only"/>
</xs:choice>
<xs:attributeGroup ref="response-identification"/>
<xs:attribute name="answer" type="xs:string"/>
<xs:attribute name="samples" type="xs:string">
<xs:annotation>
<xs:documentation>
Sample Points.
Format:
1. A comma-separated list of the variables you wish to interpret,
2. followed by â@â (not in quotes),
3. followed by any number of the following two things, separated by semi-colons:
(a) a comma-separated list of as many numbers as there are variables, which specifies one sampling point, OR
(b) a comma-separated list of as many numbers as there are variables, followed by a colon, followed by another list of as many numbers as there are variables, followed by a #, followed by an integer.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="preprocess" type="xs:string">
<xs:annotation>
<xs:documentation>
Pre-Processor Subroutine
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:annotation>
<xs:documentation>
Math response
</xs:documentation>
</xs:annotation>
<xs:element name="mathresponse">
<xs:annotation>
<xs:documentation>
Query for text that is evaluated with a script written in a computer algebra system language by the problem author.
MathResponse is extremely powerful, as it tests answers for conditions rather than agreement with a particular correct answer. An unfortunate byproduct, however, is that it cannot be analyzed by several of the LON-CAPA statistics tools.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="responseparam"/>
<xs:element name="answer" type="mathresponse--answer">
<xs:annotation>
<xs:documentation>
Maxima or R script using the arrays RESPONSE and LONCAPALIST.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element ref="textfield"/>
<xs:element ref="hiddensubmission"/>
<xs:element ref="hiddenline"/>
<xs:element ref="hintgroup"/>
<xs:element ref="mathhint"/>
<xs:element ref="hintpart"/>
<xs:group ref="text-only"/>
</xs:choice>
<xs:attributeGroup ref="response-identification"/>
<xs:attribute name="answerdisplay" type="xs:string">
<xs:annotation>
<xs:documentation>
String to display for answer
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="cas" use="required">
<xs:annotation>
<xs:documentation>
Algebra System. Maxima and R are supported.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="maxima"/>
<xs:enumeration value="R"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="args" type="xs:string">
<xs:annotation>
<xs:documentation>
Argument Array
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="libraries" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:complexType mixed="true" name="mathresponse--answer">
<xs:annotation>
<xs:documentation>Answer algorithm</xs:documentation>
</xs:annotation>
<xs:attribute name="type" type="xs:string"/>
<xs:attribute fixed="preserve" ref="xml:space"/>
</xs:complexType>
<xs:annotation>
<xs:documentation>
Function plot response
</xs:documentation>
</xs:annotation>
<xs:element name="functionplotresponse">
<xs:annotation>
<xs:documentation>
Query for the drawing of a function.
Requires that the student creates a plot that matches specified criteria.
Examples can be functions that have certain slopes, curvature, maxima or minima at specified independent coordinate values. The students create their answer by dragging the curves and adjusting the slopes.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="functionplotelements"/>
<xs:element ref="functionplotruleset"/>
<xs:element ref="hintgroup"/>
<xs:group ref="text-only"/>
</xs:choice>
<xs:attributeGroup ref="response-identification"/>
<xs:attribute name="width" type="int-or-perl">
<xs:annotation>
<xs:documentation>
Width (pixels)
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="height" type="int-or-perl">
<xs:annotation>
<xs:documentation>
Height (pixels)
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="xlabel" type="xs:string">
<xs:annotation>
<xs:documentation>
Label x-axis
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="-10" name="xmin" type="real-or-perl">
<xs:annotation>
<xs:documentation>
Minimum x-value
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="10" name="xmax" type="real-or-perl">
<xs:annotation>
<xs:documentation>
Maximum x-value
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="yes" name="xaxisvisible" type="yesno-or-perl">
<xs:annotation>
<xs:documentation>
x-axis visible
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="ylabel" type="xs:string">
<xs:annotation>
<xs:documentation>
Label y-axis
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="-10" name="ymin" type="real-or-perl">
<xs:annotation>
<xs:documentation>
Minimum y-value
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="10" name="ymax" type="real-or-perl">
<xs:annotation>
<xs:documentation>
Maximum y-value
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="yes" name="yaxisvisible" type="yesno-or-perl">
<xs:annotation>
<xs:documentation>
y-axis visible
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="yes" name="gridvisible" type="yesno-or-perl">
<xs:annotation>
<xs:documentation>
This determines whether or not the grid is on the graph.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="answerdisplay" type="xs:string">
<xs:annotation>
<xs:documentation>
Background plot(s) for answer (function(x):xmin:xmax,function(x):xmin:xmax,x1:y1:sx1:sy1:x2:y2:sx2:sy2,...)
This is a green curve the computer will display once the correct answer has been submitted. It is static, and can be given as a piecewise function.
Since some problems will have multiple correct answers, this necessarily will only be a possible answer. Only the left hand side of the equation is necessary. For example, entering x + 2 will display the line y = x + 2.
The syntax must be syntax recognized by GeoGebra. To test syntax for Geogebra directly, visit http://www.geogebra.org/webstart/geogebra.html .
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="functionplotelements">
<xs:annotation>
<xs:documentation>Function Plot Elements</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="spline"/>
<xs:element ref="backgroundplot"/>
<xs:element ref="plotobject"/>
<xs:element ref="plotvector"/>
<xs:element ref="drawvectorsum"/>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:element name="spline">
<xs:annotation>
<xs:documentation>
At least one spline is necessary for a graph problem. These splines are what will be adjusted and analyzed to solve the problem.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute name="index">
<xs:annotation>
<xs:documentation>
This is the label assigned to the spline. In general, it's simplest just to label them A, B, C etc.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="[a-zA-Z_]+"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="order">
<xs:annotation>
<xs:documentation>
This determines the number of Control Points on the spline. For example, selecting '3' means there will be 3 points on the spline that can be moved, as well as 3 points off the spline that will control the slope.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:int">
<xs:minInclusive value="2"/>
<xs:maxInclusive value="8"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="initx" type="real-or-perl">
<xs:annotation>
<xs:documentation>
Initial x-value
"Initial x-value" and "Initial y-value" determine where the left most Control Point will be.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="inity" type="real-or-perl">
<xs:annotation>
<xs:documentation>
Initial y-value
"Initial x-value" and "Initial y-value" determine where the left most Control Point will be.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="scalex" type="real-or-perl">
<xs:annotation>
<xs:documentation>
This determines the right most location of the Control Points (on the spline). To figure out where this point will be, add 'Initial x-value' to 'Scale x'.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="scaley" type="real-or-perl">
<xs:annotation>
<xs:documentation>
This determines the distance (in the y-direction) between the Control Points on the spline, and the ones that control the slope.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="backgroundplot">
<xs:annotation>
<xs:documentation>
Background Function Plot.
This places a static curve on the graph of your choosing. It can be labeled, moveable or fixed, and any color desired. Only the right hand side of the function you want displayed is necessary. For example, entering x+2 will display the line y=x+2. The syntax must be syntax recognized by GeoGebra.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute name="function" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>
An equals sign is not necessary. Just give the right hand side of the function. LON-CAPA variables are usable as well to allow individualized problems for each student. The syntax must be syntax recognized by GeoGebra.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="xinitial" type="real-or-perl">
<xs:annotation>
<xs:documentation>
Initial x-value (optional)
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="xfinal" type="real-or-perl">
<xs:annotation>
<xs:documentation>
Final x-value (optional)
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="label" type="xs:string">
<xs:annotation>
<xs:documentation>
Label on Plot
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="color">
<xs:annotation>
<xs:documentation>
Color of the background function (hex code).
The default is 000000 (black). It is recommended to choose a color other than green, since it is easily confused with being the answer.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="[\da-fA-F]{6}"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute default="yes" name="fixed" type="yesno-or-perl">
<xs:annotation>
<xs:documentation>
Fixed location
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="plotobject">
<xs:annotation>
<xs:documentation>
This places a point in the applet. Generally intended to be used with Vectors to create problems involving Free-Body Diagrams or any other points that vectors (or arrows) connect to and from.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute name="label" type="xs:string">
<xs:annotation>
<xs:documentation>
Label on Plot
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="x" type="real-or-perl"/>
<xs:attribute name="y" type="real-or-perl"/>
</xs:complexType>
</xs:element>
<xs:element name="plotvector">
<xs:annotation>
<xs:documentation>
This creates a vector (or arrow) in the applet. Generally intended to be used with Objects to create problems involving Free-Body Diagrams or to establish connections between Objects.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute name="label" type="xs:string">
<xs:annotation>
<xs:documentation>
Label on Plot.
Determines the name of the vector, as well as the name that will be visible in the problem.
This value MUST be capitalized and cannot include spaces or most symbols. To be safe, stick with letters and numbers.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="tailx" type="real-or-perl"/>
<xs:attribute name="taily" type="real-or-perl"/>
<xs:attribute name="tipx" type="real-or-perl"/>
<xs:attribute name="tipy" type="real-or-perl"/>
</xs:complexType>
</xs:element>
<xs:element name="drawvectorsum">
<xs:annotation>
<xs:documentation>Draw Vector Sum</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute name="label" type="xs:string">
<xs:annotation>
<xs:documentation>
Label on Plot
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="tailx" type="real-or-perl"/>
<xs:attribute name="taily" type="real-or-perl"/>
<xs:attribute default="yes" name="showvalue" type="yesno-or-perl"/>
<xs:attribute name="vectorlist" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:element name="functionplotruleset">
<xs:annotation>
<xs:documentation>
This is where the rules are defined. These rules will determine whether or not an entered answer is correct or not. If there are no rules, any answer will be deemed correct. If there is more than one rule, when an answer is submitted, the server will analyze them in order until one of them is broken (of course, if it's a correct answer, it will go through all of them and return a green box). In such an event, any subsequent rules will be ignored. If conditional hints related to these rules are added, only the first broken rule's hint will be shown, even if all rules are broken.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="functionplotrule"/>
<xs:element ref="functionplotvectorrule"/>
<xs:element ref="functionplotvectorsumrule"/>
<xs:element ref="functionplotcustomrule"/>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:element name="functionplotrule">
<xs:annotation>
<xs:documentation>
Function Plot Graph Rule.
Used to create a rule that determines whether or not a submitted graph is correct. In general, it takes the form of testing the function, its integral, or its first or second derivative over a given set of x-values. The test can be to see if it equals, is greater than, or less than a specified value. Anywhere a number is needed, a variable can also be used.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute name="index" type="xs:string">
<xs:annotation>
<xs:documentation>
This is an internal label for the rule. Something must be entered here, and it must be different for each rule. This same value will be used to add a conditional hint.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="0" name="derivativeorder">
<xs:annotation>
<xs:documentation>
This determines what the server will be testing. For instance, choose 'First derivative' causes the server to evaluate the derivative of the entered answer over the given domain.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:int">
<xs:enumeration value="0"/>
<xs:enumeration value="1"/>
<xs:enumeration value="2"/>
<xs:enumeration value="-1"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="xinitial" type="real-or-perl">
<xs:annotation>
<xs:documentation>
Initial x-value.
A value must be entered for one of "Initial x-value" and "Initial x-value label". Either choose a numerical value for x (the first option), or choose the beginning of the submitted answer, the end, or a previously chosen named point.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="xinitiallabel">
<xs:annotation>
<xs:documentation>
Initial x-value label.
A value must be entered for one of "Initial x-value" and "Initial x-value label". Either choose a numerical value for x (the first option), or choose the beginning of the submitted answer, the end, or a previously chosen named point.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="[a-zA-Z_]+"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="xfinal" type="real-or-perl">
<xs:annotation>
<xs:documentation>
Final x-value (optional).
This determines the end of the domain over which the rule examines. To test only a single point (the initial value), leave "Final x-value" and "Final x-value label" blank. If a label is entered, such as 'positive', the point at which the rule fails will be given this special label. This label can then be used in subsequent rules as an 'Initial x-value label'.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="xfinallabel">
<xs:annotation>
<xs:documentation>
Final x-value label (optional).
This determines the end of the domain over which the rule examines. To test only a single point (the initial value), leave "Final x-value" and "Final x-value label" blank. If a label is entered, such as 'positive', the point at which the rule fails will be given this special label. This label can then be used in subsequent rules as an 'Initial x-value label'.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="[a-zA-Z_]+"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="minimumlength" type="real-or-perl">
<xs:annotation>
<xs:documentation>
Minimum length for range (optional).
This tests that the difference between the initial and final x-values are at least a certain length apart. This is only useful if there is at least one label.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="maximumlength" type="real-or-perl">
<xs:annotation>
<xs:documentation>
Maximum length for range (optional).
This tests that the difference between the initial and final x-values are at most a certain length apart. This is only useful if there is at least one label.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="eq" name="relationship">
<xs:annotation>
<xs:documentation>
The heart of the rule. This choice determines whether the chosen 'function' is greater than, less than, equal to, etc. a certain 'value'.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="eq"/>
<xs:enumeration value="ne"/>
<xs:enumeration value="ge"/>
<xs:enumeration value="gt"/>
<xs:enumeration value="lt"/>
<xs:enumeration value="le"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute default="undef" name="value" type="xs:string">
<xs:annotation>
<xs:documentation>
Enter the number you wish to compare to. It is also possible to choose 'not defined', in the event the answer should not have a value for the given domain. Within the value argument, the function itself can be evaluated using &fpr_f(), its derivative using &fpr_dfdx(), and its second derivative using &fpr_d2fdx2(). This allows for a comparison of two points on the graph. The value of a previously defined label can be retrieved using the function &fpr_val(), e.g., &fpr_val('positive'). Previous defined values from script blocks can also be retrieved as normal variables, e.g., $x.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="percenterror" type="real-or-perl">
<xs:annotation>
<xs:documentation>
This allows for a margin of error in the y-direction. For instance, if the rule requires that the derivative be equal to 5, the server will accept values close enough to 5 that are within the percent error defined here. Note: Choosing 10% would not mean that the answer is correct as long as it is within the range 4.5-5.5. Instead, the percent corresponds to the total size of the graph. For the function itself, the 'percent error' is multiplied by the ymax-ymin; for the first derivative, it's multiplied by (ymax-ymin)/(xmax-xmin); for the second derivative, it's multiplied by (ymax-ymin)/(xmax-xmin)2; and for the integral, it's multiplied by (ymax-ymin)*(xmax-xmin).
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="functionplotvectorrule">
<xs:annotation>
<xs:documentation>
Function Plot Vector Rule
Used to test whether vectors are in the right place, pointed in the right direction, and have the correct length.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute name="index" type="xs:string">
<xs:annotation>
<xs:documentation>
Index/Name
This is an internal label for the rule. This attribute must be different for each rule. This same value will be used to add a conditional hint.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="vector" type="xs:string">
<xs:annotation>
<xs:documentation>
The name of one of the vectors in the list of function plot elements. Specifically, the one you want to test.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="attachpoint" type="xs:string">
<xs:annotation>
<xs:documentation>
Attached to object.
Object(s) this vector should be attached to. For more than one object, separate them by commas. If the vector should not be attached to any object, leave this blank. In this case, an object is considered attached if its tail OR its tip is in the vicinity of the object.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="notattachpoint" type="xs:string">
<xs:annotation>
<xs:documentation>
Not attached to object.
Object(s) that this vector should be not be near.
For more than one object, separate them by commas. Particularly useful for distractor vectors.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="tailpoint" type="xs:string">
<xs:annotation>
<xs:documentation>
Tail attached to object.
Tail(s) this vector should be attached to.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="tippoint" type="xs:string">
<xs:annotation>
<xs:documentation>
Tip attached to object.
Tip(s) this vector should be attached to.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="nottailpoint" type="xs:string">
<xs:annotation>
<xs:documentation>
Tail not attached to object.
Tail(s) this vector should not be attached to.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="nottippoint" type="xs:string">
<xs:annotation>
<xs:documentation>
Tip not attached to object.
Tip(s) this vector should not be attached to.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="length" type="real-or-perl">
<xs:annotation>
<xs:documentation>
How long the vector should be.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="lengtherror" type="real-or-perl">
<xs:annotation>
<xs:documentation>
Absolute error length.
How accurate the length must be to get the answer correct.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="angle" type="real-or-perl">
<xs:annotation>
<xs:documentation>
What direction should the vector point. All values are measured in degrees, counterclockwise starting at the positive x-axis. Values must be 0 ⤠θ < 360.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="angleerror" type="real-or-perl">
<xs:annotation>
<xs:documentation>
Absolute error angle.
How accurate the angle must be to get the answer correct.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="functionplotvectorsumrule">
<xs:annotation>
<xs:documentation>
Function Plot Vector Sum Rule
Used to test the sum of a set of vectors.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute name="index" type="xs:string">
<xs:annotation>
<xs:documentation>
Index/Name.
This is an internal label for the rule. It must be different for each rule. This same value will be used to add a conditional hint.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="vectors" type="xs:string">
<xs:annotation>
<xs:documentation>
Comma-separated list of vectors.
List all of the vectors that should be added up to be tested.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="length" type="real-or-perl">
<xs:annotation>
<xs:documentation>
Sum vector length.
How long the sum of these vectors should be.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="lengtherror" type="real-or-perl">
<xs:annotation>
<xs:documentation>
Absolute error length.
How accurate the length must be to get the answer correct.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="angle" type="real-or-perl">
<xs:annotation>
<xs:documentation>
Sum vector angle.
What direction should the sum of these vectors point. All values are measured in degrees, counterclockwise starting at the positive x-axis. Values must be 0 ⤠θ < 360.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="angleerror" type="real-or-perl">
<xs:annotation>
<xs:documentation>
Absolute error angle.
How accurate the angle must be to get the answer correct.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="functionplotcustomrule">
<xs:annotation>
<xs:documentation>
Used to create rules that aren't options using the other rules. The coding is done in Perl and follows Perl syntax. Any variable written inside this rule will be recognized as normal and any evaluation function can be used as well.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element name="answer">
<xs:annotation>
<xs:documentation>
Answer algorithm, normally in Perl
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:attribute default="loncapa/perl" name="type" type="xs:string"/>
<xs:attribute fixed="preserve" ref="xml:space"/>
</xs:complexType>
</xs:element>
</xs:choice>
<xs:attribute name="index" type="xs:string">
<xs:annotation>
<xs:documentation>
Index/Name
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:annotation>
<xs:documentation>
Organic response
</xs:documentation>
</xs:annotation>
<xs:element name="organicresponse">
<xs:annotation>
<xs:documentation>
Query for an organic chemical structure with a molecular editor.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="responseparam"/>
<xs:element ref="hiddenline"/>
<xs:element ref="hintgroup"/>
<xs:element ref="organichint"/>
<xs:element ref="hintpart"/>
<xs:group ref="text-only"/>
</xs:choice>
<xs:attributeGroup ref="response-identification"/>
<xs:attribute default="autoez" name="options">
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="(autoez|multipart|nostereo|reaction|number)(\s*,\s*(autoez|multipart|nostereo|reaction|number))*"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="molecule" type="xs:string">
<xs:annotation>
<xs:documentation>
Starting Molecule
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="answer" type="xs:string">
<xs:annotation>
<xs:documentation>
Correct Answer
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="jmeanswer" type="xs:string">
<xs:annotation>
<xs:documentation>
JME string of the answer
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="width" type="int-or-perl">
<xs:annotation>
<xs:documentation>
Width of correct answer image
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:annotation>
<xs:documentation>
Reaction response
</xs:documentation>
</xs:annotation>
<xs:element name="reactionresponse">
<xs:annotation>
<xs:documentation>
Query for a chemical reaction.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="responseparam"/>
<xs:element ref="hiddenline"/>
<xs:element ref="hintgroup"/>
<xs:element ref="reactionhint"/>
<xs:element ref="hintpart"/>
<xs:group ref="text-only"/>
</xs:choice>
<xs:attributeGroup ref="response-identification"/>
<xs:attribute name="answer" type="xs:string" use="required"/>
<xs:attribute name="initial" type="xs:string">
<xs:annotation>
<xs:documentation>
Initial Reaction
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:annotation>
<xs:documentation>
Custom response
</xs:documentation>
</xs:annotation>
<xs:element name="customresponse">
<xs:annotation>
<xs:documentation>
Query for text without any constraint (any character is allowed). A script analyzes the answer to grade it automatically.
The use of this response type is generally discouraged, since the responses will not be analyzable by the LON-CAPA statistics tools.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="responseparam"/>
<xs:element name="answer" type="customresponse--answer"/>
<xs:element ref="textfield"/>
<xs:element ref="hiddensubmission"/>
<xs:element ref="hiddenline"/>
<xs:element ref="hintgroup"/>
<xs:element ref="customhint"/>
<xs:element ref="hintpart"/>
<xs:group ref="text-only"/>
</xs:choice>
<xs:attributeGroup ref="response-identification"/>
<xs:attribute name="answerdisplay" type="xs:string">
<xs:annotation>
<xs:documentation>
String to display for answer
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:complexType mixed="true" name="customresponse--answer">
<xs:annotation>
<xs:documentation>
Perl script evaluating the student answer.
For a single textfield, the studentâs answer will be in a variable $submission. If the Custom Response has multiple textfields, the answers will be in an array reference, and can be accessed as $$submission[0], $$submission[1], etc.
The script must return a standard LON-CAPA response. The most common LON-CAPA responses are:
- EXACT ANS: return if solved exactly correctly
- APPROX ANS: return if solved approximately
- INCORRECT: return if not correct, uses up a try
- ASSIGNED SCORE: partial credit (also return the credit factor, e.g. return(ASSIGNED SCORE,0.3);)
- SIG FAIL, NO UNIT, EXTRA ANSWER, MISSING ANSWER, BAD FORMULA, WANTED NUMERIC, WRONG FORMAT: return if not correct for different reasons, does not use up a try
</xs:documentation>
</xs:annotation>
<xs:attribute name="type" type="xs:string"/>
<xs:attribute fixed="preserve" ref="xml:space"/>
</xs:complexType>
<xs:annotation>
<xs:documentation>
External response
</xs:documentation>
</xs:annotation>
<xs:element name="externalresponse">
<xs:annotation>
<xs:documentation>
Query for a long text or a line, sent to an external program for grading.
The form sent will consist of:
- LONCAPA student response full text of what the student entered in the entry field
- LONCAPA correct answer contents of the answer attribute
- LONCAPA language specified language encoding of the requesting resource
- all items in the form attribute if any of these clash with the above, the above values will overwite the value in the form attribute
The response of the remote server needs to be in XML as follows:
- loncapagrade: takes no attributes, but must surround the response.
- awarddetail: required. The delimited text inside must be one of the detailed results that appears in the data storage documentation. CVS:loncapa/doc/homework/datastorage, look for resource.partid.responseid.awarddetail.
- message: optional message to have shown to the student.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="textfield"/>
<xs:element ref="hiddensubmission"/>
<xs:element ref="hiddenline"/>
<xs:element ref="hintgroup"/>
<xs:group ref="text-only"/>
</xs:choice>
<xs:attributeGroup ref="response-identification"/>
<xs:attribute name="url" type="xs:anyURI">
<xs:annotation>
<xs:documentation>
url to submit the answer form to. It does not need to be a LON-CAPA machine.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="answer" type="xs:string">
<xs:annotation>
<xs:documentation>
data to post in the form element LONCAPA_correct_answer to the remote site.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="form" type="xs:string">
<xs:annotation>
<xs:documentation>
hash variable name that will be submitted to the remote site as a HTTP form.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="answerdisplay" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:annotation>
<xs:documentation>
Data response
</xs:documentation>
</xs:annotation>
<xs:element name="dataresponse">
<xs:annotation>
<xs:documentation>
Query for text or numbers.
Advanced type of response that implements a simple data storage and needs an input tag, such as textline, to work correctly.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="textfield"/>
<xs:element ref="hiddensubmission"/>
<xs:element ref="hiddenline"/>
<xs:group ref="text-only"/>
</xs:choice>
<xs:attributeGroup ref="response-identification"/>
<xs:attribute name="type" type="xs:string">
<xs:annotation>
<xs:documentation>
type of data stored in this response field. It should be one of the types supported by parameter.html
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="display" type="xs:string">
<xs:annotation>
<xs:documentation>
string that will be used to describe the field when interfacing with humans.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:annotation>
<xs:documentation>
Shared response elements
</xs:documentation>
</xs:annotation>
<xs:element name="responseparam">
<xs:annotation>
<xs:documentation>
Parameters for a response
Defines an externally adjustable parameter for the question, which the question can then use to allow other users to customize the problem for their courses without changing the source code of the problem.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute name="name" type="xs:string"/>
<xs:attribute name="type" type="xs:string" use="required"/>
<xs:attribute name="default" type="xs:string">
<xs:annotation>
<xs:documentation>
default value
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="description" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:element name="textfield">
<xs:annotation>
<xs:documentation>
Large Text Entry Area, contains the text that appears by default
Creates a large text input box. If data appears between the start and end tags, the data will appear in the textfield if the student has not yet made a submission.
Additionally, it takes two attributes: rows and cols, which control the height and width of the text area respectively.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="tex"/>
<xs:element ref="web"/>
</xs:choice>
<xs:attribute default="10" name="rows" type="int-or-perl"/>
<xs:attribute default="80" name="cols" type="int-or-perl">
<xs:annotation>
<xs:documentation>
Columns
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="addchars" type="xs:string">
<xs:annotation>
<xs:documentation>
Click-On Texts (comma sep)
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="spellcheck">
<xs:annotation>
<xs:documentation>
Spellcheck for
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="none"/>
<xs:enumeration value="en"/>
<xs:enumeration value="de"/>
<xs:enumeration value="he"/>
<xs:enumeration value="es"/>
<xs:enumeration value="fr"/>
<xs:enumeration value="pt"/>
<xs:enumeration value="tr"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="textline">
<xs:annotation>
<xs:documentation>
Single Line Text Entry Area. Displays a field to enter text for a response.
Should only be used inside stringresponse, numericalresponse, formularesponse, mathresponse, organicresponse, reactionresponse and customresponse.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute default="20" name="size" type="int-or-perl">
<xs:annotation>
<xs:documentation>
controls the width of the textline
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="addchars" type="xs:string">
<xs:annotation>
<xs:documentation>
Comma-separated list of characters or words that can be inserted with a click.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="no" name="readonly" type="yesno-or-perl"/>
<xs:attribute name="spellcheck">
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="none"/>
<xs:enumeration value="en"/>
<xs:enumeration value="de"/>
<xs:enumeration value="he"/>
<xs:enumeration value="es"/>
<xs:enumeration value="fr"/>
<xs:enumeration value="pt"/>
<xs:enumeration value="tr"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="hiddensubmission">
<xs:annotation>
<xs:documentation>
This creates a hidden form field with the given value.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute name="value" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:element name="hiddenline">
<xs:annotation>
<xs:documentation>
This creates a hidden form field with the old response value.
</xs:documentation>
</xs:annotation>
<xs:complexType>
</xs:complexType>
</xs:element>
<xs:annotation>
<xs:documentation>
Hints
</xs:documentation>
</xs:annotation>
<xs:element name="hintgroup">
<xs:annotation>
<xs:documentation>
The first part of the hint is the condition, which includes a specification of the foil(s) and foil answer(s) required to trigger the hint. The answers specified in the hint condition are compared with the user's submission, and if the condition is met, the hint action included in the conditional hint block will be executed (for example this could be the display of a block of text). You can set multiple hint conditions for a particular problem. Hint conditions are identified by a name. The corresponding hint action includes this hint condition name in the "on" parameter. When a hint condition evaluates to true, the corresponding hint action is triggered. Besides providing hint actions within <hintpart on="NAME"> </hintpart> tags for each named (NAME) hint condition, a hint can be designated for display if none of the conditional hints evaluate to true. The default hint is not displayed if the conditions were met for any of the conditional hints. The defau!
lt hint action is included between <hintpart on="default"> </hintpart> tags.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
<xs:element ref="hintpart"/>
<xs:element ref="stringhint"/>
<xs:element ref="radiobuttonhint"/>
<xs:element ref="optionhint"/>
<xs:element ref="numericalhint"/>
<xs:element ref="formulahint"/>
<xs:element ref="mathhint"/>
<xs:element ref="organichint"/>
<xs:element ref="reactionhint"/>
<xs:element ref="customhint"/>
</xs:choice>
<xs:attribute default="no" name="showoncorrect" type="yesno-or-perl">
<xs:annotation>
<xs:documentation>
Show hint even if problem Correct
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="hintpart">
<xs:annotation>
<xs:documentation>
Conditional Hint
When a hint tag named the same as the on attribute evaluates to be correct, the hintpart will show.
If no other hintpart is to show then all hintparts with an on value set to âdefaultâ will show.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
<xs:element ref="parserlib"/>
<xs:element ref="scriptlib"/>
</xs:choice>
<xs:attribute name="on" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="stringhint">
<xs:annotation>
<xs:documentation>
String Hint Condition
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="responseparam"/>
</xs:choice>
<xs:attribute name="id" type="xs:string"/>
<xs:attribute name="name" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>
Name of the hint condition.
Should be set to the value of which hintpart will be shown.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="answer" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>
Text string.
Should be set to the value of which hintpart will be shown.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="cs" name="type">
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="cs"/>
<xs:enumeration value="ci"/>
<xs:enumeration value="mc"/>
<xs:enumeration value="re"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="preprocess" type="xs:string">
<xs:annotation>
<xs:documentation>
Pre-Processor Subroutine
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="radiobuttonhint">
<xs:annotation>
<xs:documentation>
Radiobutton Hint Condition
The radiobutton hint tag takes two parameters: answer and name. The name is the name of the hint condition, and the answer is an array. The first element of the array will be 'foil'; the remaining elements are the names of the foils that you require to have been checked by the student for the hint to be displayed. For example, if you create a radiobutton response problem with six foils named: granite, gabbro, gneiss, shale, sandstone and schist, and you want your hint named: igneous to be displayed when either granite or basalt had been checked your radiobutton hint would be as follows:
<radiobuttonhint answer="('foil','granite','gabbro')" name="igneous"></radiobuttonhint>
In order to trigger display of this hint you also need to create a <hintpart> </hintpart> block that will include a textblock that contains the text of the actual hint.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="responseparam"/>
</xs:choice>
<xs:attribute name="id" type="xs:string"/>
<xs:attribute name="name" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>
should be set to the value of which hintpart will be shown
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="answer" type="xs:string">
<xs:annotation>
<xs:documentation>
should be at least a two element list: first the type (foil or concept) and then either the foil name(s) or the concept string(s), e.g., â(âfoilâ,âgreaterthanâ,âequalâ)â if the condition should be triggered by the foils named âgreaterthanâ or âequalâ
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="optionhint">
<xs:annotation>
<xs:documentation>
Option Response Hint Condition
There are two types of option response hint conditions: one for standalone foils and one for concept groups. In both cases the option hint tag includes two parameters: answer and name for standalone foils, and concept and name for foils grouped together in a concept group. For the answer parameter, the names and submitted values for each of the foils that are being included in the hint condition are provided in a hash, i.e., in the format: ('Foil1'= > 'True','Foil2'= > 'False'). In the case of a conditional hint for a concept group, the format of the concept parameter is also a hash that links the name of each concept group included in the hint condition to either 'correct' or 'incorrect' - e.g., < optionhint concept="('buoyancy'= > 'correct','density'= > 'correct')" name="fluids" / > If 'correct' is specified for a named concept then when the conditional hint is evaluated answers for each of the foils selected by a student must be correct for the h!
int action to be triggered. If anything other than 'correct' is provided in the concept hash in the optionhint tag then then students answers will be compared with the set answers for the foils in the concept group and as long as at least one answer is incorrect (i.e., the concept group was not correctly answered) then the corresponding hint action will be triggered.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="responseparam"/>
</xs:choice>
<xs:attribute name="id" type="xs:string"/>
<xs:attribute name="name" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>
should be set to the value of which hintpart will be shown
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="answer" type="xs:string"/>
<xs:attribute name="concept" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:element name="numericalhint">
<xs:annotation>
<xs:documentation>
Numerical Hint Condition
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="responseparam"/>
</xs:choice>
<xs:attribute name="id" type="xs:string"/>
<xs:attribute name="name" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>
Unique name given to the hint condition.
Should be set to the value of which hintpart will be shown.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="answer" type="xs:string">
<xs:annotation>
<xs:documentation>
Numerical answer for which the conditional is provided.
Student submission of that answer in combination with the "unit" attribute in the hint condition will trigger the hint action specified in the <hintpart> element.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="unit" type="xs:string"/>
<xs:attribute name="format" type="xs:string"/>
<xs:attribute name="preprocess" type="xs:string">
<xs:annotation>
<xs:documentation>
Pre-Processor Subroutine
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="formulahint">
<xs:annotation>
<xs:documentation>
Formula Hint Condition
The formula submitted by the student is evaluated at the sample points for the hint and the calculated values are compared with the corresponding values determined by evaluating the "hint" answer at the same sampling points. A close correspondence between the two sets of values will trigger the hint action specified in the <hintpart> element.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="responseparam"/>
</xs:choice>
<xs:attribute name="id" type="xs:string"/>
<xs:attribute name="name" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>
Unique name given to the hint condition.
Should be set to the value of which hintpart will be shown.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="answer" type="xs:string">
<xs:annotation>
<xs:documentation>
Formula answer for which the conditional is provided.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="samples" type="xs:string">
<xs:annotation>
<xs:documentation>
Sample points (or range of points) over which sampling of the studentâs submitted answer and the formula included in the formula hint answer parameter are to be compared. The syntax is the same as used to specify sampling points in the samples
parameter of the formula reponse element itself.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="preprocess" type="xs:string">
<xs:annotation>
<xs:documentation>
Pre-Processor Subroutine
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="mathhint">
<xs:annotation>
<xs:documentation>Math Hint Condition</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="responseparam"/>
<xs:element name="answer" type="mathhint--answer"/>
</xs:choice>
<xs:attribute name="id" type="xs:string"/>
<xs:attribute name="name" type="xs:string"/>
<xs:attribute default="maxima" name="cas">
<xs:annotation>
<xs:documentation>
Algebra System
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="maxima"/>
<xs:enumeration value="R"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="args" type="xs:string">
<xs:annotation>
<xs:documentation>
Argument Array
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="organichint">
<xs:annotation>
<xs:documentation>Organic Hint Condition</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="responseparam"/>
</xs:choice>
<xs:attribute name="id" type="xs:string"/>
<xs:attribute name="name" type="xs:string"/>
<xs:attribute name="answer" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:element name="reactionhint">
<xs:annotation>
<xs:documentation>Reaction Hint Condition</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="responseparam"/>
</xs:choice>
<xs:attribute name="id" type="xs:string"/>
<xs:attribute name="name" type="xs:string"/>
<xs:attribute name="answer" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:element name="customhint">
<xs:annotation>
<xs:documentation>
Custom Hint Condition
Define the hint condition within an answer block inside of the customhint block. The condition is defined like how an answer is defined in customresponse where you need to return EXACT ANS to indicate when customhint criteria are met.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="responseparam"/>
<xs:element name="answer" type="customhint--answer"/>
</xs:choice>
<xs:attribute name="id" type="xs:string"/>
<xs:attribute name="name" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>
should be set to the value of which hintpart will be shown
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:complexType mixed="true" name="customhint--answer">
<xs:annotation>
<xs:documentation>Hint algorithm</xs:documentation>
</xs:annotation>
<xs:attribute name="type" type="xs:string"/>
<xs:attribute fixed="preserve" ref="xml:space"/>
</xs:complexType>
<xs:complexType mixed="true" name="mathhint--answer">
<xs:annotation>
<xs:documentation>Hint algorithm</xs:documentation>
</xs:annotation>
<xs:attribute name="type" type="xs:string"/>
<xs:attribute fixed="preserve" ref="xml:space"/>
</xs:complexType>
<xs:annotation>
<xs:documentation>
Random label
</xs:documentation>
</xs:annotation>
<xs:element name="randomlabel">
<xs:annotation>
<xs:documentation>
Randomly labeled image
This shows a specified image with images or text labels randomly assigned to a set of specific locations. Those locations may also have values assigned to them. A hash is generated that contains the mapping of labels to locations, labels to values, and locations to values.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element name="labelgroup" type="randomlabel--labelgroup">
<xs:annotation>
<xs:documentation>
One is required, but multiple are allowed. This declares a group of locations and labels associated with them.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="bgimg">
<xs:annotation>
<xs:documentation>
Element alternative to the bgimg attribute, which makes it possible to use a plot as a background image.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="gnuplot"/>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:choice>
<xs:attribute name="bgimg" type="xs:anyURI">
<xs:annotation>
<xs:documentation>
Either a fully qualified URL for an external image or a LON-CAPA resource. It supports relative references (../images/apicture.gif). The image must either be a GIF or JPEG.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="width" type="int-or-perl">
<xs:annotation>
<xs:documentation>
The width of the image in pixels.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="height" type="int-or-perl">
<xs:annotation>
<xs:documentation>
The height of the image in pixels.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="texwidth" type="decimal-or-perl">
<xs:annotation>
<xs:documentation>
The width of the image in millimeters.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:complexType name="randomlabel--labelgroup">
<xs:annotation>
<xs:documentation>Group of Labels</xs:documentation>
</xs:annotation>
<xs:choice maxOccurs="unbounded">
<xs:element name="label" type="randomlabel--label"/>
<xs:element name="location" type="randomlabel--location">
<xs:annotation>
<xs:documentation>
declares a location on the image that a label should appear at
</xs:documentation>
</xs:annotation>
</xs:element>
</xs:choice>
<xs:attribute name="name" type="xs:string">
<xs:annotation>
<xs:documentation>
This is the name of the group.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="text" name="type">
<xs:annotation>
<xs:documentation>
the type of labels in this group
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="text"/>
<xs:enumeration value="image"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute default="\normalsize" name="TeXsize">
<xs:annotation>
<xs:documentation>
TeX font size
Warning: as opposed to the TeXsize attribute in <h1>..<h6> <font> and <basefont>, this one requires a \ at the beginning of the values.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="\tiny"/>
<xs:enumeration value="\scriptsize"/>
<xs:enumeration value="\footnotesize"/>
<xs:enumeration value="\small"/>
<xs:enumeration value="\normalsize"/>
<xs:enumeration value="\large"/>
<xs:enumeration value="\Large"/>
<xs:enumeration value="\LARGE"/>
<xs:enumeration value="\huge"/>
<xs:enumeration value="\Huge"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
<xs:complexType mixed="true" name="randomlabel--label">
<xs:annotation>
<xs:documentation>Label Text or Path to image</xs:documentation>
</xs:annotation>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
<xs:element ref="preduedate"/>
<xs:element ref="postanswerdate"/>
<xs:element ref="parserlib"/>
<xs:element ref="scriptlib"/>
</xs:choice>
<xs:attribute name="description" type="xs:string"/>
</xs:complexType>
<xs:complexType name="randomlabel--location">
<xs:annotation>
<xs:documentation>Label Location</xs:documentation>
</xs:annotation>
<xs:attribute name="x" type="int-or-perl" use="required"/>
<xs:attribute name="y" type="int-or-perl" use="required"/>
<xs:attribute name="value" type="xs:string"/>
</xs:complexType>
<xs:annotation>
<xs:documentation>
Gnuplot
</xs:documentation>
</xs:annotation>
<xs:element name="gnuplot">
<xs:annotation>
<xs:documentation>
The gnuplot LON-CAPA tag allows an author to design a plot which will be created programatically at the time when it is requested for display by a student. This is intended for use in homework problems where a distinct plot should be rendered for each student. It can be used in conjunction with a script to generate curve data for random plots.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="gnuplot-children"/>
</xs:choice>
<xs:attribute default="dynamically generated plot" name="alttag" type="xs:string">
<xs:annotation>
<xs:documentation>
Brief description of the plot.
This text is used as the alt value of the img tag used to display the plot on a web page.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="300" name="height" type="int-or-perl">
<xs:annotation>
<xs:documentation>
Height of image (pixels)
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="400" name="width" type="int-or-perl">
<xs:annotation>
<xs:documentation>
Width of image (pixels)
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="xffffff" name="bgcolor" type="color-or-perl">
<xs:annotation>
<xs:documentation>
Background color of image
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="x000000" name="fgcolor" type="color-or-perl">
<xs:annotation>
<xs:documentation>
Foreground color of image
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="off" name="transparent" type="onoff-or-perl">
<xs:annotation>
<xs:documentation>
Transparent image. If the image is transparent the background color will be ignored.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="on" name="grid" type="onoff-or-perl">
<xs:annotation>
<xs:documentation>
Display grid
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="off" name="gridlayer" type="onoff-or-perl">
<xs:annotation>
<xs:documentation>
Display grid front layer over filled boxes or filled curves
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="noborder" name="box_border">
<xs:annotation>
<xs:documentation>
Draw border for boxes
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="border"/>
<xs:enumeration value="noborder"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute default="on" name="border" type="onoff-or-perl">
<xs:annotation>
<xs:documentation>
Draw border around plot
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="9" name="font">
<xs:annotation>
<xs:documentation>
Font size to use in web output (in pts, or "small", "medium" or "large").
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:int">
<xs:minInclusive value="5"/>
<xs:maxInclusive value="15"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="small"/>
<xs:enumeration value="medium"/>
<xs:enumeration value="large"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute default="sans-serif" name="fontface">
<xs:annotation>
<xs:documentation>
Type of font to use
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="sans-serif"/>
<xs:enumeration value="serif"/>
<xs:enumeration value="classic"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute default="100" name="samples">
<xs:annotation>
<xs:documentation>
Number of samples for non-data plots.
If a function element is used to specify the curve, this indicates the number of sample points to use.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:int">
<xs:minInclusive value="100"/>
<xs:maxInclusive value="5000"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute default="middle" name="align">
<xs:annotation>
<xs:documentation>
Alignment for image in HTML
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="left"/>
<xs:enumeration value="right"/>
<xs:enumeration value="middle"/>
<xs:enumeration value="center"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute default="93" name="texwidth" type="int-or-perl">
<xs:annotation>
<xs:documentation>
Width of plot when printed (mm)
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="22" name="texfont">
<xs:annotation>
<xs:documentation>
Font size to use in TeX output (pts)
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:int">
<xs:minInclusive value="8"/>
<xs:maxInclusive value="36"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute default="monochrome" name="plotcolor">
<xs:annotation>
<xs:documentation>
Color setting for printing
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="monochrome"/>
<xs:enumeration value="color"/>
<xs:enumeration value="colour"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="pattern">
<xs:annotation>
<xs:documentation>
Pattern value for boxes
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:int">
<xs:minInclusive value="0"/>
<xs:maxInclusive value="6"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute default="0" name="solid" type="int-or-perl">
<xs:annotation>
<xs:documentation>
The density of fill style for boxes
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="empty" name="fillstyle">
<xs:annotation>
<xs:documentation>
Filled style for boxes
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="empty"/>
<xs:enumeration value="solid"/>
<xs:enumeration value="pattern"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute default="Cartesian" name="plottype">
<xs:annotation>
<xs:documentation>
Plot type
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="Cartesian"/>
<xs:enumeration value="Polar"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute default="Cartesian" name="gridtype">
<xs:annotation>
<xs:documentation>
Grid type
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="Cartesian"/>
<xs:enumeration value="Polar"/>
<xs:enumeration value="Linear-Log"/>
<xs:enumeration value="Log-Linear"/>
<xs:enumeration value="Log-Log"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute default="default" name="lmargin">
<xs:annotation>
<xs:documentation>
Left margin width (pts)
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl xs:int">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="default"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute default="default" name="rmargin">
<xs:annotation>
<xs:documentation>
Right margin width (pts)
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl xs:int">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="default"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute default="default" name="tmargin">
<xs:annotation>
<xs:documentation>
Top margin width (pts)
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl xs:int">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="default"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute default="default" name="bmargin">
<xs:annotation>
<xs:documentation>
Bottom margin width (pts)
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl xs:int">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="default"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="boxwidth" type="xs:string">
<xs:annotation>
<xs:documentation>
Width of boxes, default is auto
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="1" name="major_ticscale" type="real-or-perl">
<xs:annotation>
<xs:documentation>
Size of major tic marks (plot coordinates)
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="0.5" name="minor_ticscale" type="real-or-perl">
<xs:annotation>
<xs:documentation>
Size of minor tic mark (plot coordinates)
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:group name="gnuplot-children">
<xs:annotation>
<xs:documentation>
List of children, used in gnuplot and lonplot--block
</xs:documentation>
</xs:annotation>
<xs:choice>
<xs:element name="title" type="lonplot--title"/>
<xs:element name="axis" type="lonplot--axis">
<xs:annotation>
<xs:documentation>
The Plot Axes tag allows you to specify the domain and range of the data to display. It is closely tied with the Plot Ticks tags, which specify where the gridlines are drawn on the plot.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="curve" type="lonplot--curve">
<xs:annotation>
<xs:documentation>
The curve tag is where you set the data to be plotted by gnuplot.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="key" type="lonplot--key">
<xs:annotation>
<xs:documentation>
The key tag causes a key to be drawn on the plot when it is generated. The key will contain an entry for each curve which has a name.
The key is the color of the foreground of the plot, specified in the gnuplot tag.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="label" type="lonplot--label">
<xs:annotation>
<xs:documentation>
The label tag allows the author to place text at any position on the plot. There may be many label tags on one plot and all the labels which fall within the plot will show. The color used will be to foreground color of the plot and the font will be the size specified for the plot, both of which are set in the gnuplot tag.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="xtics" type="lonplot--xtics"/>
<xs:element name="ytics" type="lonplot--ytics"/>
<xs:element name="xlabel" type="lonplot--xlabel"/>
<xs:element name="ylabel" type="lonplot--ylabel"/>
<xs:element name="block" type="lonplot--block"/>
</xs:choice>
</xs:group>
<xs:simpleType name="lonplot--title">
<xs:annotation>
<xs:documentation>Plot Title</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:string"/>
</xs:simpleType>
<xs:complexType name="lonplot--axis">
<xs:annotation>
<xs:documentation>Plot axes</xs:documentation>
</xs:annotation>
<xs:attribute default="x000000" name="color" type="color-or-perl">
<xs:annotation>
<xs:documentation>
Color of grid lines
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="-10.0" name="xmin" type="real-or-perl">
<xs:annotation>
<xs:documentation>
Minimum x-value shown in plot
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="10.0" name="xmax" type="real-or-perl">
<xs:annotation>
<xs:documentation>
Maximum x-value shown in plot
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="-10.0" name="ymin" type="real-or-perl">
<xs:annotation>
<xs:documentation>
Minimum y-value shown in plot
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="10.0" name="ymax" type="real-or-perl">
<xs:annotation>
<xs:documentation>
Maximum y-value shown in plot
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="on" name="xformat">
<xs:annotation>
<xs:documentation>
X-axis number formatting
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="on"/>
<xs:enumeration value="off"/>
<xs:enumeration value="2e"/>
<xs:enumeration value="2f"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute default="on" name="yformat">
<xs:annotation>
<xs:documentation>
Y-axis number formatting
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="on"/>
<xs:enumeration value="off"/>
<xs:enumeration value="2e"/>
<xs:enumeration value="2f"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute default="off" name="xzero">
<xs:annotation>
<xs:documentation>
Show x-zero (y=0) axis
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="off"/>
<xs:enumeration value="line"/>
<xs:enumeration value="thick-line"/>
<xs:enumeration value="dotted"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute default="off" name="yzero">
<xs:annotation>
<xs:documentation>
Show y-zero (x=0) axis
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="off"/>
<xs:enumeration value="line"/>
<xs:enumeration value="thick-line"/>
<xs:enumeration value="dotted"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
<xs:complexType name="lonplot--curve">
<xs:annotation>
<xs:documentation>Plot Curve</xs:documentation>
</xs:annotation>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element name="data" type="lonplot--data"/>
<xs:element name="function" type="lonplot--function"/>
</xs:choice>
<xs:attribute default="x000000" name="color" type="color-or-perl">
<xs:annotation>
<xs:documentation>
Color of curve
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="name" type="xs:string">
<xs:annotation>
<xs:documentation>
Name of curve to appear in key
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="lines" name="linestyle">
<xs:annotation>
<xs:documentation>
Unless otherwise noted the linestyles require only 2 data sets, X and Y.
- lines: Connect adjacent points with straight line segments.
- points: Display a small marker at each point.
- linespoints: Draw both lines and points.
Draws a small symbol at each point and then connects adjacent points with straight line segments.
- dots: Place a tiny dots on the given points.
- steps: Connect points with horizontal lines.
This style connects consecutive points with two line segments: the first from (x1,y1) to (x2,y1) and the second from (x2,y1) to (x2,y2).
- fsteps: Connect data with horizontal lines.
This style connects consecutive points with two line segments: the first from (x1,y1) to (x1,y2) and the second from (x1,y2) to (x2,y2).
- histeps: Plot as histogram.
Y-values are assumed to be centered at the x-values; the point at x1 is represented as a horizontal line from ((x0+x1)/2,y1) to ((x1+x2)/2,y1). The lines representing the end points are extended so that the step is centered on at x. Adjacent points are connected by a vertical line at their average x, that is, from ((x1+x2)/2,y1) to ((x1+x2)/2,y2).
- errorbars: Same as yerrorbars.
- xerrorbars: Draw horizontal error bars around the points.
Requires 3 or 4 data sets. Either X, Y, Xdelta or X, Y, Xlower, Xupper. Xdelta is a change relative to the given X value. The Xlower and Xupper values are absolute grid coordinates of the upper and lower values to indicated with error bars.
- yerrorbars: Draw vertical error bars around the points.
Requires 3 or 4 data sets. Either X, Y, Ydelta or X, Y, Ylower, Yupper. Ydelta is a change relative to the given Y value. The Ylower and Yupper values are the grid coordinates of the upper and lower values to indicate with error bars.
- xyerrorbars: Draw both vertical and horizontal error bars around the points.
Requires 4 or 6 data sets. Either X, Y, Xdelta, Ydelta or X, Y, Xlower, Xupper, Ylower, Yupper. Xdelta and Ydelta are relative to the given coordinates. Xlower, Xupper, Ylower, and Yupper are the grid coordinates of the upper and lower values to indicate with the error bars.
- boxes: Draw a box from the X-axis to the Y-value given.
Requires either 2 or 3 data sets. Either X, Y or X, Y, Xwidth. In the first case the boxes will be drawn next to eachother. In the latter case Xwidth indicates the horizontal width of the box for the given coordinate.
- vector: Draws a vector field based on the given data.
Requires 4 data sets, X, Y, Xdelta, and Ydelta. The âvectorâ style draws a vector from (X,Y) to (X+Xdelta,Y+Ydelta). It also draws a small arrowhead at the end of the vector. May not be fully supported by gnuplot.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="lines"/>
<xs:enumeration value="linespoints"/>
<xs:enumeration value="dots"/>
<xs:enumeration value="points"/>
<xs:enumeration value="steps"/>
<xs:enumeration value="fsteps"/>
<xs:enumeration value="histeps"/>
<xs:enumeration value="errorbars"/>
<xs:enumeration value="xerrorbars"/>
<xs:enumeration value="yerrorbars"/>
<xs:enumeration value="xyerrorbars"/>
<xs:enumeration value="boxes"/>
<xs:enumeration value="filledcurves"/>
<xs:enumeration value="vector"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute default="1" name="linewidth">
<xs:annotation>
<xs:documentation>
Line width (may not apply to all plot styles)
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:int">
<xs:minInclusive value="1"/>
<xs:maxInclusive value="10"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute default="solid" name="linetype">
<xs:annotation>
<xs:documentation>
Line type (may not apply to all plot styles)
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="solid"/>
<xs:enumeration value="dashed"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute default="1" name="pointsize" type="int-or-perl">
<xs:annotation>
<xs:documentation>
Point size (may not apply to all plot styles)
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="1" name="pointtype">
<xs:annotation>
<xs:documentation>
Point type (may not apply to all plot styles)
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:int">
<xs:minInclusive value="0"/>
<xs:maxInclusive value="6"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute default="closed" name="limit">
<xs:annotation>
<xs:documentation>
Point to fill for filled curves
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="above"/>
<xs:enumeration value="below"/>
<xs:enumeration value="closed"/>
<xs:enumeration value="x1"/>
<xs:enumeration value="x2"/>
<xs:enumeration value="y1"/>
<xs:enumeration value="y2"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute default="head" name="arrowhead">
<xs:annotation>
<xs:documentation>
For vector plots, controls where in the vector the arrow head(s) appear.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="nohead"/>
<xs:enumeration value="head"/>
<xs:enumeration value="heads"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute default="filled" name="arrowstyle">
<xs:annotation>
<xs:documentation>
For vector plots, controls the fill style of the arrow.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="filled"/>
<xs:enumeration value="empty"/>
<xs:enumeration value="nofilled"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute default="0.02" name="arrowlength" type="real-or-perl">
<xs:annotation>
<xs:documentation>
For vector plots, determines the distance between the vector line end and the tip of the arrow.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="10.0" name="arrowangle" type="real-or-perl">
<xs:annotation>
<xs:documentation>
For vector plots, determines the angle the arrow branches make with the vector line.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="90.0" name="arrowbackangle" type="real-or-perl">
<xs:annotation>
<xs:documentation>
For vector plots, determines the angle the arrow lines that return to the main line from the branches make with the arrow branches.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:complexType mixed="true" name="lonplot--data">
<xs:annotation>
<xs:documentation>
Curve data
The data must be either a perl array, @X, or a comma separated list, such as â0.5,0.9,1.5,2.4â (without quotes). âNaNâ is a valid value. Note the the âYâ values are entered in a separate array.
</xs:documentation>
</xs:annotation>
</xs:complexType>
<xs:complexType mixed="true" name="lonplot--function">
<xs:annotation>
<xs:documentation>
Used to specify the curve to be plotted as a formula, instead of numerical data.
The function must be a mathematical expression. Use the independent variable âxâ for cartesian plots and âtâ for polar plots. Implicit multiplication is not accepted by Gnuplot.
</xs:documentation>
</xs:annotation>
</xs:complexType>
<xs:complexType name="lonplot--key">
<xs:annotation>
<xs:documentation>
Causes a key to be drawn on the plot when it is generated. The key will contain an entry for each curve which has a name.
The key is the color of the foreground of the plot, specified in the gnuplot tag.
</xs:documentation>
</xs:annotation>
<xs:attribute name="title" type="xs:string">
<xs:annotation>
<xs:documentation>
Title of key
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="off" name="box" type="onoff-or-perl">
<xs:annotation>
<xs:documentation>
Draw a box around the key?
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="top right" name="pos">
<xs:annotation>
<xs:documentation>
Position of the key on the plot
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="top left"/>
<xs:enumeration value="top right"/>
<xs:enumeration value="bottom left"/>
<xs:enumeration value="bottom right"/>
<xs:enumeration value="outside"/>
<xs:enumeration value="below"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
<xs:complexType mixed="true" name="lonplot--label">
<xs:annotation>
<xs:documentation>Plot Label</xs:documentation>
</xs:annotation>
<xs:attribute default="0" name="xpos" type="real-or-perl">
<xs:annotation>
<xs:documentation>
X position of label (graph coordinates)
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="0" name="ypos" type="real-or-perl">
<xs:annotation>
<xs:documentation>
Y position of label (graph coordinates)
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="left" name="justify">
<xs:annotation>
<xs:documentation>
justification of the label text on the plot
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="left"/>
<xs:enumeration value="right"/>
<xs:enumeration value="center"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute default="0" name="rotate" type="real-or-perl">
<xs:annotation>
<xs:documentation>
Rotation of label (degrees)
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:complexType abstract="true" name="lonplot--tics">
<xs:annotation>
<xs:documentation>Plot tics</xs:documentation>
</xs:annotation>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element name="tic">
<xs:annotation>
<xs:documentation>
The <tic> tag allows users to specify exact Tic positions and labels for each axis.
In this version we only support level 0 tics (major tic).
Each tic has associated with it a position and a label $current_tics is a reference to the current tick description hash.
We add elements to an array in that has: ticspecs whose elements are 'pos' - the tick position and 'label' - the tic label.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:attribute name="location" type="real-or-perl" use="required"/>
</xs:complexType>
</xs:element>
</xs:choice>
<xs:attribute default="border" name="location">
<xs:annotation>
<xs:documentation>
Location of major tic marks
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="border"/>
<xs:enumeration value="axis"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute default="on" name="mirror" type="onoff-or-perl">
<xs:annotation>
<xs:documentation>
Mirror tics on opposite axis?
If the location of tic marks is set to âborderâ this parameter determines if they are shown on both the top and bottom or right and left sides of the graph. The âmirrorâ tic marks are unlabelled.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="-10.0" name="start" type="real-or-perl">
<xs:annotation>
<xs:documentation>
The point in graph coordinates which to start making major tics. This may be less than or greater than the lower limit for the axis.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="1.0" name="increment" type="real-or-perl">
<xs:annotation>
<xs:documentation>
The span, in graph coordinates, between each major tic mark.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="10.0" name="end" type="real-or-perl">
<xs:annotation>
<xs:documentation>
Stop major tics at.
This may be less than or greater than the upper limit for the axis.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="0" name="minorfreq" type="int-or-perl">
<xs:annotation>
<xs:documentation>
The number of subdivisions to make of the span between major tic marks. Using a value of â10â leads to 9 minor tic marks.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="off" name="rotate" type="onoff-or-perl">
<xs:annotation>
<xs:documentation>
For output devices that support it, this rotates the tic label by 90 degrees. This is most useful with large lables defined by the tic tag described below.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:complexType name="lonplot--xtics">
<xs:annotation>
<xs:documentation>Plot xtics</xs:documentation>
</xs:annotation>
<xs:complexContent>
<xs:extension base="lonplot--tics"/>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="lonplot--ytics">
<xs:annotation>
<xs:documentation>Plot ytics</xs:documentation>
</xs:annotation>
<xs:complexContent>
<xs:extension base="lonplot--tics"/>
</xs:complexContent>
</xs:complexType>
<xs:simpleType name="lonplot--xlabel">
<xs:annotation>
<xs:documentation>Plot x-label</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:string"/>
</xs:simpleType>
<xs:simpleType name="lonplot--ylabel">
<xs:annotation>
<xs:documentation>Plot y-label</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:string"/>
</xs:simpleType>
<xs:complexType name="lonplot--block">
<xs:annotation>
<xs:documentation>
Conditional Block
This has a required argument condition that is evaluated. If the condition is true, everything inside the tag is evaluated; otherwise, everything inside the block tag is skipped.
When found inside the gnuplot element, a block can only have gnuplot children inside, with no text.
</xs:documentation>
</xs:annotation>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="gnuplot-children"/>
</xs:choice>
<xs:attribute name="condition" type="xs:string">
<xs:annotation>
<xs:documentation>
Test Condition
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:annotation>
<xs:documentation>
Task
</xs:documentation>
</xs:annotation>
<xs:element name="Task">
<xs:annotation>
<xs:documentation>Root for .task (bridge task) documents</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="IntroParagraph"/>
<xs:element ref="Setup"/>
<xs:element ref="Question"/>
<xs:element ref="Criteria"/>
<xs:element ref="ClosingParagraph"/>
</xs:choice>
<xs:attribute name="OptionalRequired" type="int-or-perl">
<xs:annotation>
<xs:documentation>
Required number of passed optional elements to pass the Task
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="IntroParagraph">
<xs:annotation>
<xs:documentation>Introductory Information</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
<xs:element ref="parserlib"/>
<xs:element ref="scriptlib"/>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:element name="ClosingParagraph">
<xs:annotation>
<xs:documentation>Closing Information</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
<xs:element ref="parserlib"/>
<xs:element ref="scriptlib"/>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:element name="Question">
<xs:annotation>
<xs:documentation>Question</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="Instance"/>
<xs:element ref="QuestionText"/>
<xs:element ref="Question"/>
<xs:element ref="Criteria"/>
</xs:choice>
<xs:attribute name="id" type="xs:string"/>
<xs:attribute default="Y" name="Mandatory">
<xs:annotation>
<xs:documentation>
Passing is Mandatory
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="Y"/>
<xs:enumeration value="N"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="OptionalRequired" type="int-or-perl">
<xs:annotation>
<xs:documentation>
Required number of passed optional elements to pass
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="QuestionText">
<xs:annotation>
<xs:documentation>Question Information</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
<xs:element ref="parserlib"/>
<xs:element ref="scriptlib"/>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:element name="Setup">
<xs:annotation>
<xs:documentation>Setup....</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="Instance"/>
</xs:choice>
<xs:attribute name="id" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:element name="Instance">
<xs:annotation>
<xs:documentation>Specific Question Instance</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="InstanceText"/>
<xs:element ref="Criteria"/>
</xs:choice>
<xs:attribute name="id" type="xs:string"/>
<xs:attribute default="no" name="Disabled" type="yesno-or-perl">
<xs:annotation>
<xs:documentation>
Instance is Disabled
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="OptionalRequired" type="int-or-perl">
<xs:annotation>
<xs:documentation>
Required number of passed optional elements to pass the Instance
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="InstanceText">
<xs:annotation>
<xs:documentation>Information for the Instance</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
<xs:element ref="parserlib"/>
<xs:element ref="scriptlib"/>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:element name="Criteria">
<xs:annotation>
<xs:documentation>Question Criteria</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="GraderNote"/>
<xs:element ref="CriteriaText"/>
</xs:choice>
<xs:attribute name="id" type="xs:string"/>
<xs:attribute default="Y" name="Mandatory">
<xs:annotation>
<xs:documentation>
Passing is Mandatory
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="Y"/>
<xs:enumeration value="N"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="CriteriaText">
<xs:annotation>
<xs:documentation>Criteria Information</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
<xs:element ref="parserlib"/>
<xs:element ref="scriptlib"/>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:element name="GraderNote">
<xs:annotation>
<xs:documentation>Text to display to Grader</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
<xs:element ref="parserlib"/>
<xs:element ref="scriptlib"/>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:annotation>
<xs:documentation>
Problem block elements that cannot be used anywhere text is used.
</xs:documentation>
</xs:annotation>
<xs:element name="part">
<xs:annotation>
<xs:documentation>
Problem Part
This must be below problem if it is going to be used. It does many of the same tasks as problem, but allows multiple separate problems to exist in a single file.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-with-responses"/>
<xs:group ref="inserts"/>
<xs:element ref="parameter"/>
<xs:element ref="parserlib"/>
<xs:element ref="scriptlib"/>
</xs:choice>
<xs:attribute name="id" type="xs:string">
<xs:annotation>
<xs:documentation>
Part ID
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="display" type="xs:string">
<xs:annotation>
<xs:documentation>
Displayed Part Description
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="allow">
<xs:annotation>
<xs:documentation>File Dependencies</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute name="src" type="xs:string">
<xs:annotation>
<xs:documentation>
Path to the file
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="parserlib" type="xs:anyURI">
<xs:annotation>
<xs:documentation>
Import Tag Definitions
The enclosed filename contains definitions for new tags.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="scriptlib" type="xs:anyURI">
<xs:annotation>
<xs:documentation>
Import Script Library
The enclosed filename contains Perl code to run in the safe space.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="meta">
<xs:annotation>
<xs:documentation>
Custom Metadata for LON-CAPA (as opposed to the HTML meta which should be inside <head>).
Recognized names:
abstract, author, authorspace, avetries, avetries_list, clear, comefrom, comefrom_list, copyright, correct, count, course, course_list, courserestricted, creationdate, dependencies, depth, difficulty, difficulty_list, disc, disc_list, domain, end, field, firstname, generation, goto, goto_list, groupname, helpful, highestgradelevel, hostname, id, keynum, keywords, language, lastname, lastrevisiondate, lowestgradelevel, middlename, mime, modifyinguser, notes, owner, permanentemail, scope, sequsage, sequsage_list, standards, start, stdno, stdno_list, subject, technical, title, url, username, value, version.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute name="name" type="xs:string"/>
<xs:attribute name="content" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:element name="parameter">
<xs:annotation>
<xs:documentation>
Parameter for a part
parameter is exactly the same as responseparam, but should appear outside of a response tag.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute name="id" type="xs:string"/>
<xs:attribute name="name" type="xs:string"/>
<xs:attribute name="type" type="xs:string" use="required"/>
<xs:attribute name="description" type="xs:string"/>
<xs:attribute name="default" type="xs:string"/>
<xs:attribute name="display" type="xs:string">
<xs:annotation>
<xs:documentation>
Title displayed on the parameter setting screen.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="displaytitle">
<xs:annotation>
<xs:documentation>
This will insert the title of the problem from the metadata of the problem. Only the first displaytitle in a problem will show the title; this allows clean usage of displaytitle in LON-CAPA style files.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute name="style" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:element name="displayduedate">
<xs:annotation>
<xs:documentation>
This will insert the current due date if one is set in the document.
It is generated to be inside a table of 1x1 elements.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute name="style" type="xs:string">
<xs:annotation>
<xs:documentation>
style=âplainâ Makes the due date appear without any boxing. If the parameter value is other than âplainâ, or if the style parameter is omitted, the due date will be displayed within a box.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="format" type="xs:string">
<xs:annotation>
<xs:documentation>
Allows you to control the format of the due date. This is an arbitrary string that can contain any of the following formatting items:
%a Replaced by the abbreviated weekday name according to the current locale.
%A Replaced by the full weekday name according to the current locale.
%b The abbreviated month name according to the current locale.
%B The full month name according to the current locale.
%c The preferred date and time representation for the current locale (the default format string is just this).
%C The century number as a two digit integer
%d The day of the month as a decimal number. Leading zeroes are shown for single digit day numbers.
%D Equivalent to %m/%d/%y
%e Like %d but a leadnig zero is replaced by a space.
%F Equivalent to %Y-%m-%d
%G The four digit year number.
%g The two digit year numbger.
%H The hour as a two digit number in the range 00 thorugh 23.
%I The hour as a two digit number in the range 00 through 12.
%j The day your the year in the range 001 through 366.
%k The hour (24 hour clock), single digits are preceded by a blank.
%l Like %k but using a 12 hour clock.
%m The month as a two digit decimal number in the range 01 through 12.
%M The minute as a two digit decimal number in the range 00 through 59.
%n A newline character.
%p AM or PM depending on the time value.
%P am or pm.
%r The time in am or pm notation.
%R Time in 24 hour notatinon (%H:%M). See also %T below.
%s Number of seconds since midnight of January 1, 1970.
%S The second as a decimal number int the range 00 through 59.
%t A horizontal tab character.
%T The time in 24 hour notation (%H:%M:%S).
%u Day of the week as a decimal number with Monday as 1.
%U The week number of the current year in the range 00 through 53. Week 1 is the week containing the first Sunday of the year.
%V Same as %U but week 1 is the first week with at least 4 days, with Monday being the first day of a week.
%w Day of the week as a decimal integer in the range 0 through 7, Sunday is 0.
%W Week number of the current year in the range 00 through 53, where the first Monday of the year is the first day of week 01.
%x The preferred date notation in the current locale without the time.
%X The preferred time notation in the current locale without the date.
%y The year as a decimal number without the century (range 00 through 99).
%Y The year as a decimal number including the century.
%% A % character.
%+ Date and time in the form returned by the Unix date command.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="preduedate">
<xs:annotation>
<xs:documentation>
Before Due Date Block
Everything inside is skipped if the problem is after the due date.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-with-responses"/>
<xs:element ref="displayduedate"/>
<xs:element ref="displaytitle"/>
<xs:element ref="parserlib"/>
<xs:element ref="scriptlib"/>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:element name="postanswerdate">
<xs:annotation>
<xs:documentation>
After Answer Date Block
Everything inside is skipped if the problem is before the answer date.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
<xs:element ref="displayduedate"/>
<xs:element ref="displaytitle"/>
<xs:element ref="parserlib"/>
<xs:element ref="scriptlib"/>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:element name="solved">
<xs:annotation>
<xs:documentation>
Block For After Solved
Everything inside is skipped if the problem part is ânot solvedâ.
Should not be used outside of parts in a problem using parts.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
<xs:group ref="inserts"/>
<xs:element ref="parserlib"/>
<xs:element ref="scriptlib"/>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:element name="notsolved">
<xs:annotation>
<xs:documentation>
Block For When Not Solved
Everything inside is skipped if the problem part is âsolvedâ.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-with-responses"/>
<xs:group ref="inserts"/>
<xs:element ref="parserlib"/>
<xs:element ref="scriptlib"/>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:annotation>
<xs:documentation>
Non-HTML block elements mixed with text
</xs:documentation>
</xs:annotation>
<xs:element name="import">
<xs:annotation>
<xs:documentation>
Import a File
This causes the parse to read in the file named in the body of the tag and parse it as if the entire text of the file had existed at the location of the tag.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:anyURI">
<xs:attribute name="id" type="xs:string"/>
<xs:attribute name="importmode">
<xs:annotation>
<xs:documentation>
Import as
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="problem"/>
<xs:enumeration value="part"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
<xs:complexType mixed="true" name="block-base">
<xs:annotation>
<xs:documentation>
Conditional Block
This has a required argument condition that is evaluated. If the condition is true, everything inside the tag is evaluated; otherwise, everything inside the block tag is skipped.
</xs:documentation>
</xs:annotation>
<xs:attribute name="condition" type="xs:string">
<xs:annotation>
<xs:documentation>
Test Condition
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:complexType mixed="true" name="block-with-parts">
<xs:complexContent mixed="true">
<xs:extension base="block-base">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-with-parts"/>
<xs:group ref="inserts"/>
<xs:element ref="parserlib"/>
<xs:element ref="scriptlib"/>
</xs:choice>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType mixed="true" name="block-with-responses">
<xs:complexContent mixed="true">
<xs:extension base="block-base">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-with-responses"/>
<xs:group ref="inserts"/>
<xs:element ref="parserlib"/>
<xs:element ref="scriptlib"/>
</xs:choice>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType mixed="true" name="block-with-text">
<xs:complexContent mixed="true">
<xs:extension base="block-base">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
<xs:group ref="inserts"/>
<xs:element ref="parserlib"/>
<xs:element ref="scriptlib"/>
</xs:choice>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:annotation>
<xs:documentation>
see also: lonplot--block
</xs:documentation>
</xs:annotation>
<xs:element name="while">
<xs:annotation>
<xs:documentation>
While Loop Block
This implements a while loop. The required attribute condition is a Perl scriptlet that when evaluated results in a true or false value. If true, the entirety of the text between the whiles is parsed. The condition is tested again, etc. If false, it goes to the next tag.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
<xs:group ref="inserts"/>
<xs:element ref="parserlib"/>
<xs:element ref="scriptlib"/>
</xs:choice>
<xs:attribute name="condition" type="xs:string">
<xs:annotation>
<xs:documentation>
Test Condition
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="tex" type="xs:string">
<xs:annotation>
<xs:documentation>
Print Only Block (using only LaTeX)
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="print">
<xs:annotation>
<xs:documentation>
Print Only Block (using HTML)
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:element name="web">
<xs:annotation>
<xs:documentation>
Web Only Block
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:element name="standalone">
<xs:annotation>
<xs:documentation>
Everything in between the start and end tag is shown only on the web and only if the resource is not part of a course.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:complexType mixed="true" name="problemtype-base">
<xs:annotation>
<xs:documentation>
Problem Type Block
Allows you to show or hide output based on what the problem-type parameter is set to in the course.
Will only show the output text when the problem is set to the type of exam or survey in the course.
</xs:documentation>
</xs:annotation>
<xs:attribute default="show" name="mode">
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="show"/>
<xs:enumeration value="hide"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute default="exam" name="for">
<xs:annotation>
<xs:documentation>
When used as type(s)
Comma-separated list of values among:
exam, survey, surveycred, anonsurvey, anonsurveycred, problem, practice, randomizetry
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="(exam|survey|surveycred|anonsurvey|anonsurveycred|problem|practice|randomizetry)(\s*,\s*(exam|survey|surveycred|anonsurvey|anonsurveycred|problem|practice|randomizetry))*"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
<xs:complexType mixed="true" name="problemtype-with-parts">
<xs:complexContent mixed="true">
<xs:extension base="problemtype-base">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-with-parts"/>
<xs:group ref="inserts"/>
<xs:element ref="parserlib"/>
<xs:element ref="scriptlib"/>
</xs:choice>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType mixed="true" name="problemtype-with-responses">
<xs:complexContent mixed="true">
<xs:extension base="problemtype-base">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-with-responses"/>
<xs:group ref="inserts"/>
<xs:element ref="parserlib"/>
<xs:element ref="scriptlib"/>
</xs:choice>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType mixed="true" name="problemtype-with-text">
<xs:complexContent mixed="true">
<xs:extension base="problemtype-base">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
<xs:group ref="inserts"/>
<xs:element ref="parserlib"/>
<xs:element ref="scriptlib"/>
</xs:choice>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="randomlist-base">
<xs:annotation>
<xs:documentation>
Randomly Parsed Block
The enclosed tags are parsed in a stable random order. The optional attribute show=âNâ restricts the number of tags inside that are actually parsed to no more than N. N can equal the total tags inside. The randomlist tag can be used to randomize problem parts by wrapping the <part> tags with a randomlist tag. Note that when randomlist wraps <part> tags, that all students will work all parts only if show=âNâ where N is the total number of parts wrapped. When N is less than the total number of parts wrapped, there will be gaps in the assessment chart, and also in the table of submissions for each student, corresponding to those parts which are never available to that particular student.
</xs:documentation>
</xs:annotation>
<xs:attribute name="show" type="int-or-perl">
<xs:annotation>
<xs:documentation>
Maximum Tags to Show
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:complexType name="randomlist-with-parts">
<xs:complexContent>
<xs:extension base="randomlist-base">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="part"/>
<xs:group ref="responses"/>
<xs:element ref="img"/>
<xs:element ref="postanswerdate"/>
<xs:element ref="preduedate"/>
<xs:element name="block" type="block-with-parts"/>
<xs:element ref="while"/>
<xs:element name="problemtype" type="problemtype-with-parts"/>
<xs:element ref="window"/>
<xs:element ref="display"/>
<xs:element ref="gnuplot"/>
<xs:element ref="organicstructure"/>
<xs:element ref="instructorcomment"/>
<xs:element ref="drawimage"/>
<xs:element ref="import"/>
<xs:element name="section" type="section-with-parts"/>
<xs:element name="ul" type="ul-with-parts"/>
<xs:element name="ol" type="ol-with-parts"/>
<xs:element name="table" type="table-with-parts"/>
<xs:element name="dl" type="dl-with-parts"/>
<xs:element ref="object"/>
<xs:element ref="applet"/>
<xs:element ref="embed"/>
<xs:element ref="video"/>
<xs:element ref="audio"/>
<xs:element ref="canvas"/>
<xs:element ref="form"/>
<xs:element ref="iframe"/>
</xs:choice>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="randomlist-with-responses">
<xs:complexContent>
<xs:extension base="randomlist-base">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="responses"/>
<xs:element ref="img"/>
<xs:element ref="postanswerdate"/>
<xs:element ref="preduedate"/>
<xs:element name="block" type="block-with-responses"/>
<xs:element ref="while"/>
<xs:element name="problemtype" type="problemtype-with-responses"/>
<xs:element ref="window"/>
<xs:element ref="display"/>
<xs:element ref="gnuplot"/>
<xs:element ref="organicstructure"/>
<xs:element ref="instructorcomment"/>
<xs:element ref="drawimage"/>
<xs:element ref="import"/>
<xs:element name="section" type="section-with-responses"/>
<xs:element name="ul" type="ul-with-responses"/>
<xs:element name="ol" type="ol-with-responses"/>
<xs:element name="table" type="table-with-responses"/>
<xs:element name="dl" type="dl-with-responses"/>
<xs:element ref="object"/>
<xs:element ref="applet"/>
<xs:element ref="embed"/>
<xs:element ref="video"/>
<xs:element ref="audio"/>
<xs:element ref="canvas"/>
<xs:element ref="form"/>
<xs:element ref="iframe"/>
</xs:choice>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="randomlist-with-text">
<xs:complexContent>
<xs:extension base="randomlist-base">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="img"/>
<xs:element ref="postanswerdate"/>
<xs:element ref="preduedate"/>
<xs:element name="block" type="block-with-text"/>
<xs:element ref="while"/>
<xs:element name="problemtype" type="problemtype-with-text"/>
<xs:element ref="window"/>
<xs:element ref="display"/>
<xs:element ref="gnuplot"/>
<xs:element ref="organicstructure"/>
<xs:element ref="instructorcomment"/>
<xs:element ref="drawimage"/>
<xs:element ref="import"/>
<xs:element name="section" type="section-with-text"/>
<xs:element name="ul" type="ul-with-text"/>
<xs:element name="ol" type="ol-with-text"/>
<xs:element name="table" type="table-with-text"/>
<xs:element name="dl" type="dl-with-text"/>
<xs:element ref="object"/>
<xs:element ref="applet"/>
<xs:element ref="embed"/>
<xs:element ref="video"/>
<xs:element ref="audio"/>
<xs:element ref="canvas"/>
<xs:element ref="form"/>
<xs:element ref="iframe"/>
</xs:choice>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="script">
<xs:annotation>
<xs:documentation>
Perl Script Block
If the attribute type is set to âloncapa/perlâ the enclosed data is a Perl script which is evaluated inside the Perl safe space. The return value of the script is ignored.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:attribute default="text/javascript" name="type" type="xs:string"/>
<xs:attribute fixed="preserve" ref="xml:space"/>
</xs:complexType>
</xs:element>
<xs:element name="languageblock">
<xs:annotation>
<xs:documentation>
This declares the intent to provide content that can be rendered in the set of languages in the include specification but not in the exclude specification. If a currently preferred language is in the include list the content in the <languageblock>...</languageblock> is rendered If the currently preferred language is in the exclude list, the content in the <languageblock>..</languageblock is not rendered.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
</xs:choice>
<xs:attribute name="include" type="xs:string"/>
<xs:attribute name="exclude" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:element name="translated">
<xs:annotation>
<xs:documentation>
<translated> starts a block of a resource that has multiple translations.
See the <lang> tag as well.
When </translated> is encountered if there is a translation for the currently preferred language, that is rendered inthe web/tex/webgrade targets. Otherwise, the default text is rendered.
Note that <lang> is only registered for the duration of the <translated>...</translated> block.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
<xs:element ref="lang"/>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:element name="lang">
<xs:annotation>
<xs:documentation>
Specifies that the block contained within it is a translation for a specific language specified by the 'which' attribute. The 'other' attribute can be used by itself or in conjunction with which to specify this tag _may_ be used as a translation for some list of languages. e.g.:
<lang which='senisoUS' other='senisoCA,senisoAU,seniso'>
specifying that the block provides a translation for US (primary) Canadian, Australian and UK English.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
</xs:choice>
<xs:attribute name="which" type="xs:string"/>
<xs:attribute name="other" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:element name="window">
<xs:annotation>
<xs:documentation>
Text In Separate Window
This creates a link that when clicked shows the intervening information in a pop-up window. By default the window will be 500 pixels wide and 200 pixels tall, and the link text will be a superscript * (so as to look like a footnote). These can be changed using the attributes.
When printing, the included text will get turned into a real footnote.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
<xs:group ref="inserts"/>
</xs:choice>
<xs:attribute name="linktext" type="xs:string">
<xs:annotation>
<xs:documentation>
Text of Link
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="200" name="height" type="int-or-perl"/>
<xs:attribute default="500" name="width" type="int-or-perl"/>
<xs:attribute name="printtext" type="xs:string">
<xs:annotation>
<xs:documentation>
Printed text (optional)
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="windowlink">
<xs:annotation>
<xs:documentation>
This creates a link to a resource that comes up in a pop-up window.
The link will be the intervening information between the start and the end tag.
By default the window will be 500 pixels wide and 200 pixels tall.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
</xs:choice>
<xs:attribute name="href" type="xs:anyURI"/>
<xs:attribute name="height" type="int-or-perl">
<xs:annotation>
<xs:documentation>
starting height of the popup window
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="width" type="int-or-perl">
<xs:annotation>
<xs:documentation>
starting width of the popup window
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="togglebox">
<xs:annotation>
<xs:documentation>
This creates a toggling box that can be clicked open and close.
When printing, the included text will be rendered in a visible box.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
</xs:choice>
<xs:attribute name="heading" type="xs:string">
<xs:annotation>
<xs:documentation>
heading text of the box, by default no heading
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="headerbg" type="xs:string">
<xs:annotation>
<xs:documentation>
background color of the header, by default white
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="showtext" type="xs:string">
<xs:annotation>
<xs:documentation>
the text that appears to make the box visible, by default the translation of âshowâ
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="hidetext" type="xs:string">
<xs:annotation>
<xs:documentation>
the text that appears to hide the box again, by default the translation of âhideâ
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="instructorcomment">
<xs:annotation>
<xs:documentation>
Comment that is hidden if form.instructor_comments='hide'.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:element name="comment">
<xs:annotation>
<xs:documentation>
Allows one to comment out sections of code in a balanced manner, or to provide a comment description of how a problem works.
The content is ignored.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="part"/>
<xs:group ref="text-with-responses"/>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:element name="organicstructure">
<xs:annotation>
<xs:documentation>Organic Structure</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute name="width" type="int-or-perl">
<xs:annotation>
<xs:documentation>
Width (pixels)
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="texwidth" type="decimal-or-perl">
<xs:annotation>
<xs:documentation>
TeXwidth (mm)
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="molecule" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>
JME string
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="reaction" name="options">
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="reaction"/>
<xs:enumeration value="border"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="drawimage">
<xs:annotation>
<xs:documentation>
Draws an image with the specified objects using pixel coordinates (text, line, rectangle, arc, fill, polygon, image).
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element name="text">
<xs:complexType mixed="true">
<xs:attribute name="x" type="int-or-perl" use="required"/>
<xs:attribute name="y" type="int-or-perl" use="required"/>
<xs:attribute name="font" type="xs:string"/>
<xs:attribute name="color" type="xs:string"/>
<xs:attribute name="direction" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:element name="line">
<xs:complexType>
<xs:attribute name="x1" type="int-or-perl" use="required"/>
<xs:attribute name="y1" type="int-or-perl" use="required"/>
<xs:attribute name="x2" type="int-or-perl" use="required"/>
<xs:attribute name="y2" type="int-or-perl" use="required"/>
<xs:attribute name="color" type="xs:string"/>
<xs:attribute name="thickness" type="int-or-perl"/>
</xs:complexType>
</xs:element>
<xs:element name="rectangle">
<xs:complexType>
<xs:attribute name="x1" type="int-or-perl" use="required"/>
<xs:attribute name="y1" type="int-or-perl" use="required"/>
<xs:attribute name="x2" type="int-or-perl" use="required"/>
<xs:attribute name="y2" type="int-or-perl" use="required"/>
<xs:attribute name="color" type="xs:string"/>
<xs:attribute name="thickness" type="int-or-perl"/>
<xs:attribute name="filled" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:element name="arc">
<xs:complexType>
<xs:attribute name="x" type="int-or-perl" use="required"/>
<xs:attribute name="y" type="int-or-perl" use="required"/>
<xs:attribute name="width" type="int-or-perl" use="required"/>
<xs:attribute name="height" type="int-or-perl" use="required"/>
<xs:attribute name="start" type="real-or-perl"/>
<xs:attribute name="end" type="real-or-perl"/>
<xs:attribute name="color" type="xs:string"/>
<xs:attribute name="thickness" type="int-or-perl"/>
<xs:attribute name="filled" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:element name="fill">
<xs:complexType>
<xs:attribute name="x" type="int-or-perl" use="required"/>
<xs:attribute name="y" type="int-or-perl" use="required"/>
<xs:attribute name="color" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:element name="polygon">
<xs:complexType>
<xs:choice maxOccurs="unbounded">
<xs:element name="point">
<xs:complexType>
<xs:attribute name="x" type="int-or-perl" use="required"/>
<xs:attribute name="y" type="int-or-perl" use="required"/>
</xs:complexType>
</xs:element>
</xs:choice>
<xs:attribute name="color" type="xs:string"/>
<xs:attribute name="filled" type="xs:string"/>
<xs:attribute name="open" type="xs:string"/>
<xs:attribute name="thickness" type="int-or-perl"/>
</xs:complexType>
</xs:element>
<xs:element name="image">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:anyURI">
<xs:attribute name="x" type="int-or-perl" use="required"/>
<xs:attribute name="y" type="int-or-perl" use="required"/>
<xs:attribute name="clipx" type="int-or-perl"/>
<xs:attribute name="clipy" type="int-or-perl"/>
<xs:attribute name="clipwidth" type="int-or-perl"/>
<xs:attribute name="clipheight" type="int-or-perl"/>
<xs:attribute name="scaledwidth" type="int-or-perl"/>
<xs:attribute name="scaledheight" type="int-or-perl"/>
<xs:attribute name="transparent" type="xs:string"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
</xs:choice>
<xs:attribute default="300" name="width" type="int-or-perl"/>
<xs:attribute default="300" name="height" type="int-or-perl"/>
<xs:attribute name="bgcolor" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:annotation>
<xs:documentation>
Non-HTML inline elements mixed with text
</xs:documentation>
</xs:annotation>
<xs:element name="display" type="xs:string">
<xs:annotation>
<xs:documentation>
Display Script Result Block
The intervening Perl script is evaluated in the safe space and the return value of the script replaces the entire tag.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="m">
<xs:annotation>
<xs:documentation>
The inside text is LaTeX, and is converted to HTML (or MathML) on the fly.
This element is normally used for math, and the text should start and end with either $ or $$.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:attribute name="display" type="xs:string">
<xs:annotation>
<xs:documentation>
Option to force the math rendering for this element.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="off" name="eval" type="onoff-or-perl">
<xs:annotation>
<xs:documentation>
Perl variables inside the element will be evaluated if this attribute value is "on".
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="lm">
<xs:annotation>
<xs:documentation>
Inline math with the LON-CAPA syntax (use <m> for LaTeX math).
Perl variables are evaluated.
The expression is interpreted with implicit operators (for multiplication or units).
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute default="symbols" name="mode">
<xs:annotation>
<xs:documentation>
In symbols mode, names are interpreted as constants or variables.
In units mode, names are interpreted as constants or units.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="symbols"/>
<xs:enumeration value="units"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
<xs:element name="num">
<xs:annotation>
<xs:documentation>
Typesets a number formatted in scientific notation, fixed point, fixed point with commas, fixed point with commas and dollar sign, or in significant digits.
<num format="2E">31454678</num> results in 3.15 x 10^7
<num format="2f">31454678</num> results in 31454678.00
<num format="4g">31454678</num> results in 3.145 x 10^7
<num format="4g">314.54678</num> results in 314.5
<num format=",2f">31454678</num> results in 31,454,678.00
<num format="$2f">31454678</num> results in $31,454,678.00
<num format="2s">31454678</num> results in 31000000
<num format=",2s">31454678</num> results in 31,000,000
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:attribute name="format" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:element name="algebra">
<xs:annotation>
<xs:documentation>
Typesets algebraic expressions.
Expressions are displayed using the math expression display mechanism defined in the userâs preferences. The default is tth.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:attribute name="style" type="xs:string"/>
<xs:attribute name="display" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:element name="chem" type="xs:string">
<xs:annotation>
<xs:documentation>
Typesets chemical equation
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="parse" type="xs:string">
<xs:annotation>
<xs:documentation>
Evaluates the Perl content, then parses it as if it was part of the XML document and displays the result.
Warning: using this element (or the xmlparse function) will reduce the document future interoperability, because dynamically generated XML cannot be automatically converted.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="displayweight">
<xs:annotation>
<xs:documentation>
Displays the number of points awarded for this problem or problem part.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="displaystudentphoto">
<xs:complexType>
<xs:attribute name="width" type="int-or-perl"/>
<xs:attribute name="height" type="int-or-perl"/>
<xs:attribute name="align">
<xs:annotation>
<xs:documentation>
note: this attribute is not supported in HTML5, css should be used instead !
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="left"/>
<xs:enumeration value="right"/>
<xs:enumeration value="middle"/>
<xs:enumeration value="top"/>
<xs:enumeration value="bottom"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:annotation>
<xs:documentation>
HTML
</xs:documentation>
</xs:annotation>
<xs:attributeGroup name="coreattrs">
<xs:annotation>
<xs:documentation>
core attributes common to most HTML elements
</xs:documentation>
</xs:annotation>
<xs:attribute name="id" type="xs:ID">
<xs:annotation>
<xs:documentation>
This attribute defines a unique identifier (ID) which must be unique in the whole document. Its purpose is to identify the element when linking (using a fragment identifier), scripting, or styling (with CSS).
Usage note:
- This attribute's value is an opaque string: this means that web author must not use it to convey any information. Particular meaning, for example semantic meaning, must not be derived from the string.
- This attribute's value must not contain white spaces. Browsers treat non-conforming IDs that contains white spaces as if the white space is part of the ID. In contrast to the class attribute, which allows space-separated values, elements can only have one single ID defined through the id attribute. Note that an element may have several IDs, but the others should be set by another means, such as via a script interfacing with the DOM interface of the element.
- Using characters except ASCII letters and digits, '_', '-' and '.' may cause compatibility problems, as they weren't allowed in HTML 4. Though this restriction has been lifted in HTML 5, an ID should start with a letter for compatibility.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="class" type="xs:NMTOKENS">
<xs:annotation>
<xs:documentation>
This attribute is a space-separated list of the classes of the element. Classes allows CSS and Javascript to select and access specific elements via the class selectors or functions like the DOM method document.getElementsByClassName.
Usage note: Though the specification doesn't put requirements on the name of classes, web developers are encouraged to use names that describe the semantic purpose of the element, rather to the presentation of the element (e.g., attribute to describe an attribute rather than italics, although an element of this class may be presented by italics). Semantic names remain logical even if the presentation of the page changes.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="style" type="xs:string">
<xs:annotation>
<xs:documentation>
This attribute contains CSS styling declarations to be applied to the element. Note that it is recommended for styles to be defined in a separate file or files. This attribute and the <style> element have mainly the purpose of allowing for quick styling, for example for testing purposes.
Usage note: This attribute must not be used to convey semantic information. Even if all styling is removed, a page should remain semantically correct. Typically it shouldn't be used to hide irrelevant information; this should be done using the hidden attribute.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:attributeGroup name="i18n">
<xs:annotation>
<xs:documentation>
internationalization attributes
lang language code (backwards compatible)
xml:lang language code (as per XML 1.0 spec)
dir direction for weak/neutral text
</xs:documentation>
</xs:annotation>
<xs:attribute name="lang" type="xs:language"/>
<xs:attribute ref="xml:lang"/>
<xs:attribute name="dir">
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="ltr"/>
<xs:enumeration value="rtl"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:attributeGroup>
<xs:complexType mixed="true" name="inlineBaseType">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="inlines"/>
</xs:choice>
</xs:complexType>
<xs:group name="heading">
<xs:choice>
<xs:element ref="h1"/>
<xs:element ref="h2"/>
<xs:element ref="h3"/>
<xs:element ref="h4"/>
<xs:element ref="h5"/>
<xs:element ref="h6"/>
</xs:choice>
</xs:group>
<xs:complexType mixed="true" name="headerContent">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="inlines"/>
</xs:choice>
<xs:attributeGroup ref="coreattrs"/>
<xs:attribute ref="TeXsize"/>
</xs:complexType>
<xs:element name="html">
<xs:annotation>
<xs:documentation>
The HTML root element (<html>) represents the root of an HTML document.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element ref="head"/>
<xs:element ref="body"/>
</xs:sequence>
<xs:attributeGroup ref="i18n"/>
<xs:attribute name="id" type="xs:ID"/>
</xs:complexType>
</xs:element>
<xs:group name="head.misc">
<xs:sequence>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element name="script" type="htmlScript"/>
<xs:element ref="style"/>
<xs:element name="meta" type="htmlMeta">
<xs:annotation>
<xs:documentation>
generic metainformation
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element ref="link"/>
<xs:element ref="import"/>
</xs:choice>
</xs:sequence>
</xs:group>
<xs:element name="head">
<xs:annotation>
<xs:documentation>
The HTML Head Element (<head>) provides general information (metadata) about the document, including its title and links to or definitions of scripts and style sheets.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:group ref="head.misc"/>
<xs:choice>
<xs:sequence>
<xs:element ref="title"/>
<xs:group ref="head.misc"/>
<xs:sequence minOccurs="0">
<xs:element ref="base"/>
<xs:group ref="head.misc"/>
</xs:sequence>
</xs:sequence>
<xs:sequence>
<xs:element ref="base"/>
<xs:group ref="head.misc"/>
<xs:element ref="title"/>
<xs:group ref="head.misc"/>
</xs:sequence>
</xs:choice>
</xs:sequence>
<xs:attributeGroup ref="i18n"/>
<xs:attribute name="id" type="xs:ID"/>
<xs:attribute name="profile" type="xs:anyURI">
<xs:annotation>
<xs:documentation>
The URIs of one or more metadata profiles, separated by white space.
This attribute is obsolete in HTML5.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="title">
<xs:annotation>
<xs:documentation>
The title element is not considered part of the flow of text.
It should be displayed, for example as the page header or
window title. Exactly one title is required per document.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:attributeGroup ref="i18n"/>
<xs:attribute name="id" type="xs:ID"/>
</xs:complexType>
</xs:element>
<xs:element name="base">
<xs:annotation>
<xs:documentation>
Document base URI
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute name="href" type="xs:anyURI" use="required"/>
<xs:attribute name="id" type="xs:ID"/>
</xs:complexType>
</xs:element>
<xs:complexType name="htmlMeta">
<xs:attributeGroup ref="i18n"/>
<xs:attribute name="id" type="xs:ID"/>
<xs:attribute name="http-equiv">
<xs:annotation>
<xs:documentation>
This enumerated attribute defines the pragma that can alter servers and user-agents behavior. The value of the pragma is defined using the content and can be one of the following:
- content-language (obsolete)
- content-type (obsolete)
- default-style
- refresh
- set-cookie (obsolete)
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="name">
<xs:annotation>
<xs:documentation>
This attribute defines the name of a document-level metadata. It should not be set if one of the attributes itemprop, http-equiv or charset is also set.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="content" use="required">
<xs:annotation>
<xs:documentation>
This attribute gives the value associated with the http-equiv or name attribute, depending of the context.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="scheme">
<xs:annotation>
<xs:documentation>
This attribute defines the scheme in which the metadata is described. A scheme is a context leading to the correct interpretations of the content value, like a format.
Notes: Do not use this attribute as it is obsolete. There is no replacement for it as there was no real usage for it. Omit it altogether.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:simpleType name="MediaDesc">
<xs:annotation>
<xs:documentation>
Single or comma-separated list of media descriptors
</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:string">
<xs:pattern value="[^,]+(,\s*[^,]+)*"/>
</xs:restriction>
</xs:simpleType>
<xs:element name="link">
<xs:annotation>
<xs:documentation>
The HTML Link Element (<link>) specifies relationships between the current document and external resource. Possible uses for this element include defining a relational framework for navigation. This Element is most used to link to style sheets.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute name="charset" type="xs:string">
<xs:annotation>
<xs:documentation>
This attribute defines the character encoding of the linked resource. The value is a space- and/or comma-delimited list of character sets as defined in RFC 2045. The default value is ISO-8859-1.
Usage note: This attribute is obsolete in HTML5 and must not be used by authors. To achieve its effect, use the Content-Type: HTTP header on the linked resource.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="href" type="xs:anyURI">
<xs:annotation>
<xs:documentation>
This attribute specifies the URL of the linked resource. A URL might be absolute or relative.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="hreflang" type="xs:language">
<xs:annotation>
<xs:documentation>
This attribute indicates the language of the linked resource. It is purely advisory. Allowed values are determined by BCP47 for HTML5 and by RFC1766 for HTML 4. Use this attribute only if the href attribute is present.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="type" type="xs:string">
<xs:annotation>
<xs:documentation>
This attribute is used to define the type of the content linked to. The value of the attribute should be a MIME type such as text/html, text/css, and so on. The common use of this attribute is to define the type of style sheet linked and the most common current value is text/css, which indicates a Cascading Style Sheet format.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="rel" type="xs:NMTOKENS">
<xs:annotation>
<xs:documentation>
This attribute names a relationship of the linked document to the current document. The attribute must be a space-separated list of the link types values. The most common use of this attribute is to specify a link to an external style sheet: the rel attribute is set to stylesheet, and the href attribute is set to the URL of an external style sheet to format the page. WebTV also supports the use of the value next for rel to preload the next page in a document series.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="rev" type="xs:NMTOKENS">
<xs:annotation>
<xs:documentation>
The value of this attribute shows the relationship of the current document to the linked document, as defined by the href attribute. The attribute thus defines the reverse relationship compared to the value of the rel attribute. Link types values for the attribute are similar to the possible values for rel.
Usage note: This attribute is obsolete in HTML5. Do not use it. To achieve its effect, use the rel attribute with the opposite link types values, e.g. made should be replaced by author. Also this attribute doesn't mean revision and must not be used with a version number, which is unfortunately the case on numerous sites.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="media" type="MediaDesc">
<xs:annotation>
<xs:documentation>
This attribute specifies the media which the linked resource applies to. Its value must be a media query. This attribute is mainly useful when linking to external stylesheets by allowing the user agent to pick the best adapted one for the device it runs on.
Usage note:
- In HTML 4, this can only be a simple white-space-separated list of media description literals, i.e., media types and groups, where defined and allowed as values for this attribute, such as print, screen, aural, braille. HTML5 extended this to any kind of media queries, which are a superset of the allowed values of HTML 4.
- Browsers not supporting the CSS3 Media Queries won't necessarily recognize the adequate link; do not forget to set fallback links, the restricted set of media queries defined in HTML 4.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="style">
<xs:annotation>
<xs:documentation>
The HTML <style> element contains style information for a document, or a part of document. The specific style information is contained inside of this element, usually in the CSS.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:attributeGroup ref="i18n"/>
<xs:attribute name="id" type="xs:ID"/>
<xs:attribute default="text/css" name="type" type="xs:string"/>
<xs:attribute name="media" type="MediaDesc"/>
<xs:attribute name="title" type="xs:string"/>
<xs:attribute fixed="preserve" ref="xml:space"/>
</xs:complexType>
</xs:element>
<xs:complexType mixed="true" name="htmlScript">
<xs:annotation>
<xs:documentation>
The HTML <script> element is used to embed or reference an executable script within an HTML or XHTML document.
Scripts without async or defer attributes, as well as inline scripts, are fetched and executed immediately, before the browser continues to parse the page.
</xs:documentation>
</xs:annotation>
<xs:attribute name="id" type="xs:ID"/>
<xs:attribute name="charset" type="xs:string"/>
<xs:attribute name="src" type="xs:anyURI">
<xs:annotation>
<xs:documentation>
This attribute specifies the URI of an external script; this can be used as an alternative to embedding a script directly within a document. script elements with an src attribute specified should not have a script embedded within its tags.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="text/javascript" name="type" type="xs:string">
<xs:annotation>
<xs:documentation>
This attribute identifies the scripting language of code embedded within a script element or referenced via the elementâs src attribute. This is specified as a MIME type; examples of supported MIME types include text/javascript, text/ecmascript, application/javascript, and application/ecmascript.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="language" type="xs:string">
<xs:annotation>
<xs:documentation>
Like the type attribute, this attribute identifies the scripting language in use. Unlike the type attribute, however, this attributeâs possible values were never standardized. The type attribute should be used instead.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="defer">
<xs:annotation>
<xs:documentation>
This Boolean attribute is set to indicate to a browser that the script is meant to be executed after the document has been parsed. Since this feature hasn't yet been implemented by all other major browsers, authors should not assume that the scriptâs execution will actually be deferred. The defer attribute shouldn't be used on scripts that don't have the src attribute. Since Gecko 1.9.2, the defer attribute is ignored on scripts that don't have the src attribute. However, in Gecko 1.9.1 even inline scripts are deferred if the defer attribute is set.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="defer"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="async">
<xs:annotation>
<xs:documentation>
HTML5 only.
Set this Boolean attribute to indicate that the browser should, if possible, execute the script asynchronously. It has no effect on inline scripts (i.e., scripts that don't have the src attribute).
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="async"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute fixed="preserve" ref="xml:space"/>
</xs:complexType>
<xs:element name="noscript">
<xs:annotation>
<xs:documentation>
Alternate content container for non script-based rendering.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="blocks-with-text"/>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:element name="body">
<xs:annotation>
<xs:documentation>
The HTML <body> element represents the content of an HTML document. There is only one <body> element in a document.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="blocks-with-text"/>
</xs:choice>
<xs:attributeGroup ref="coreattrs"/>
<xs:attributeGroup ref="i18n"/>
<xs:attribute name="onload" type="xs:string"/>
<xs:attribute name="onunload" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:complexType mixed="true" name="section-base">
<xs:annotation>
<xs:documentation>
The HTML Section Element (<section>) represents a generic section of a document, i.e., a thematic grouping of content, typically with a heading. Each <section> should be identified, typically by including a heading (h1-h6 element) as a child of the <section> element.
</xs:documentation>
</xs:annotation>
<xs:attributeGroup ref="coreattrs"/>
</xs:complexType>
<xs:complexType mixed="true" name="section-with-parts">
<xs:complexContent mixed="true">
<xs:extension base="section-base">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-with-parts"/>
</xs:choice>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType mixed="true" name="section-with-responses">
<xs:complexContent mixed="true">
<xs:extension base="section-base">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-with-responses"/>
</xs:choice>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType mixed="true" name="section-with-text">
<xs:complexContent mixed="true">
<xs:extension base="section-base">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
</xs:choice>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="header">
<xs:annotation>
<xs:documentation>
The HTML <header> Element represents a group of introductory or navigational aids. It may contain some heading elements but also other elements like a logo, wrapped section's header, a search form, and so on.
This element should have no <footer> or <header> descendants.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:element name="footer">
<xs:annotation>
<xs:documentation>
The HTML <footer> Element represents a footer for its nearest sectioning content or sectioning root element (i.e, its nearest parent <article>, <aside>, <nav>, <section>, <blockquote>, <body>, <details>, <fieldset>, <figure>, <td>). A footer typically contains information about the author of the section, copyright data or links to related documents.
This element should have no <footer> or <header> descendants.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:element name="aside">
<xs:annotation>
<xs:documentation>
The HTML <aside> element represents a section of the page with content connected tangentially to the rest, which could be considered separate from that content. These sections are often represented as sidebars or inserts. They often contain the definitions on the sidebars, such as definitions from the glossary; there may also be other types of information, such as related advertisements; the biography of the author; web applications; profile information or related links on the blog.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:element name="h1" type="headerContent">
<xs:annotation>
<xs:documentation>
Level 1 title (most important).
A heading element briefly describes the topic of the section it introduces. Heading information may be used by user agents, for example, to construct a table of contents for a document automatically.
Inside HTML5 sections, all the heading elements can be h1 (they don't need to be h1, h2, ...). Web browsers determine the level of the heading based on the depth in the section tree.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="h2" type="headerContent">
<xs:annotation>
<xs:documentation>
Level 2 title
A heading element briefly describes the topic of the section it introduces. Heading information may be used by user agents, for example, to construct a table of contents for a document automatically.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="h3" type="headerContent">
<xs:annotation>
<xs:documentation>
Level 3 title
A heading element briefly describes the topic of the section it introduces. Heading information may be used by user agents, for example, to construct a table of contents for a document automatically.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="h4" type="headerContent">
<xs:annotation>
<xs:documentation>
Level 4 title
A heading element briefly describes the topic of the section it introduces. Heading information may be used by user agents, for example, to construct a table of contents for a document automatically.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="h5" type="headerContent">
<xs:annotation>
<xs:documentation>
Level 5 title
A heading element briefly describes the topic of the section it introduces. Heading information may be used by user agents, for example, to construct a table of contents for a document automatically.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="h6" type="headerContent">
<xs:annotation>
<xs:documentation>
Level 6 title
A heading element briefly describes the topic of the section it introduces. Heading information may be used by user agents, for example, to construct a table of contents for a document automatically.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:complexType mixed="true" name="div-base">
<xs:annotation>
<xs:documentation>
The HTML <div> element (or HTML Document Division Element) is the generic container for flow content, which does not inherently represent anything. It can be used to group elements for styling purposes (using the class or id attributes), or because they share attribute values, such as lang. It should be used only when no other semantic element (such as <article> or <nav>) is appropriate.
</xs:documentation>
</xs:annotation>
<xs:attributeGroup ref="coreattrs"/>
<xs:attribute name="align">
<xs:annotation>
<xs:documentation>
In HTML5, the align attribute on <div> is obsolete.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="center"/>
<xs:enumeration value="left"/>
<xs:enumeration value="right"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
<xs:complexType mixed="true" name="div-with-parts">
<xs:complexContent mixed="true">
<xs:extension base="div-base">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-with-parts"/>
<xs:group ref="inserts"/>
<xs:element ref="allow"/>
<xs:element ref="meta"/>
<xs:element ref="parameter"/>
<xs:element ref="parserlib"/>
<xs:element ref="scriptlib"/>
</xs:choice>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType mixed="true" name="div-with-responses">
<xs:complexContent mixed="true">
<xs:extension base="div-base">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-with-responses"/>
<xs:group ref="inserts"/>
<xs:element ref="allow"/>
<xs:element ref="meta"/>
<xs:element ref="parameter"/>
<xs:element ref="parserlib"/>
<xs:element ref="scriptlib"/>
</xs:choice>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType mixed="true" name="div-with-text">
<xs:complexContent mixed="true">
<xs:extension base="div-base">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
<xs:group ref="inserts"/>
<xs:element ref="meta"/>
<xs:element ref="parameter"/>
<xs:element ref="parserlib"/>
<xs:element ref="scriptlib"/>
</xs:choice>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType mixed="true" name="p-base">
<xs:annotation>
<xs:documentation>
The HTML <p> element (or HTML Paragraph Element) represents a paragraph of text. Paragraphs are block-level elements.
</xs:documentation>
</xs:annotation>
<xs:attributeGroup ref="coreattrs"/>
</xs:complexType>
<xs:complexType mixed="true" name="p-with-responses">
<xs:complexContent mixed="true">
<xs:extension base="p-base">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="inlines"/>
<xs:group ref="inlineResponses"/>
</xs:choice>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType mixed="true" name="p-with-text">
<xs:complexContent mixed="true">
<xs:extension base="p-base">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="inlines"/>
</xs:choice>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="ul-base">
<xs:annotation>
<xs:documentation>
Unordered list
</xs:documentation>
</xs:annotation>
<xs:attributeGroup ref="coreattrs"/>
<xs:attribute name="type" type="xs:string">
<xs:annotation>
<xs:documentation>
Used to set the bullet style for the list. The values defined under HTML3.2 and the transitional version of HTML 4.0/4.01 are:
- circle,
- disc,
- and square.
A fourth bullet type has been defined in the WebTV interface, but not all browsers support it: triangle.
If not present and if no CSS list-style-type property does apply to the element, the user agent decide to use a kind of bullets depending on the nesting level of the list.
Usage note: Do not use this attribute, as it has been deprecated; use the CSS list-style-type property instead.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:complexType name="ul-with-parts">
<xs:complexContent>
<xs:extension base="ul-base">
<xs:sequence maxOccurs="unbounded">
<xs:element name="li" type="li-with-parts"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="ul-with-responses">
<xs:complexContent>
<xs:extension base="ul-base">
<xs:sequence maxOccurs="unbounded">
<xs:element name="li" type="li-with-responses"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="ul-with-text">
<xs:complexContent>
<xs:extension base="ul-base">
<xs:sequence maxOccurs="unbounded">
<xs:element name="li" type="li-with-text"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="ol-base">
<xs:annotation>
<xs:documentation>
Ordered list
</xs:documentation>
</xs:annotation>
<xs:attributeGroup ref="coreattrs"/>
<xs:attribute name="type" type="xs:string">
<xs:annotation>
<xs:documentation>
Indicates the numbering type:
- 'a' indicates lowercase letters,
- 'A' indicates uppercase letters,
- 'i' indicates lowercase Roman numerals,
- 'I' indicates uppercase Roman numerals,
- and '1' indicates numbers (default).
The type set is used for the entire list unless a different type attribute is used within an enclosed <li> element.
Note: This attribute was deprecated in HTML4, but reintroduced in HTML5. Unless the value of the list number matters (e.g. in legal or technical documents where items are to be referenced by their number/letter), the CSS list-style-type property should be used instead.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:complexType name="ol-with-parts">
<xs:complexContent>
<xs:extension base="ol-base">
<xs:sequence maxOccurs="unbounded">
<xs:element name="li" type="li-with-parts"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="ol-with-responses">
<xs:complexContent>
<xs:extension base="ol-base">
<xs:sequence maxOccurs="unbounded">
<xs:element name="li" type="li-with-responses"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="ol-with-text">
<xs:complexContent>
<xs:extension base="ol-base">
<xs:sequence maxOccurs="unbounded">
<xs:element name="li" type="li-with-text"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType mixed="true" name="li-base">
<xs:annotation>
<xs:documentation>
List item
</xs:documentation>
</xs:annotation>
<xs:attributeGroup ref="coreattrs"/>
<xs:attribute name="type" type="xs:string">
<xs:annotation>
<xs:documentation>
This character attributes indicates the numbering type:
a: lowercase letters
A: uppercase letters
i: lowercase Roman numerals
I: uppercase Roman numerals
1: numbers
This type overrides the one used by its parent <ol> element, if any.
Usage note: This attribute has been deprecated: use the CSS list-style-type property instead.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="value" type="xs:int">
<xs:annotation>
<xs:documentation>
This integer attributes indicates the current ordinal value of the item in the list as defined by the <ol> element. The only allowed value for this attribute is a number, even if the list is displayed with Roman numerals or letters. List items that follow this one continue numbering from the value set. The value attribute has no meaning for unordered lists (<ul>) or for menus (<menu>).
Note: This attribute was deprecated in HTML4, but reintroduced in HTML5.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:complexType mixed="true" name="li-with-parts">
<xs:complexContent mixed="true">
<xs:extension base="li-base">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-with-parts"/>
</xs:choice>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType mixed="true" name="li-with-responses">
<xs:complexContent mixed="true">
<xs:extension base="li-base">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-with-responses"/>
</xs:choice>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType mixed="true" name="li-with-text">
<xs:complexContent mixed="true">
<xs:extension base="li-base">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
</xs:choice>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="dl-base">
<xs:annotation>
<xs:documentation>
The HTML <dl> Element (or HTML Description List Element) encloses a list of pairs of terms and descriptions. Common uses for this element are to implement a glossary or to display metadata (a list of key-value pairs).
Prior to HTML5, <dl> was known as a Definition List.
</xs:documentation>
</xs:annotation>
<xs:attributeGroup ref="coreattrs"/>
</xs:complexType>
<xs:complexType name="dl-with-parts">
<xs:complexContent>
<xs:extension base="dl-base">
<xs:choice maxOccurs="unbounded">
<xs:element ref="dt"/>
<xs:element name="dd" type="dd-with-parts"/>
</xs:choice>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="dl-with-responses">
<xs:complexContent>
<xs:extension base="dl-base">
<xs:choice maxOccurs="unbounded">
<xs:element ref="dt"/>
<xs:element name="dd" type="dd-with-responses"/>
</xs:choice>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="dl-with-text">
<xs:complexContent>
<xs:extension base="dl-base">
<xs:choice maxOccurs="unbounded">
<xs:element ref="dt"/>
<xs:element name="dd" type="dd-with-text"/>
</xs:choice>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="dt" type="inlineBaseType">
<xs:annotation>
<xs:documentation>
The HTML <dt> element (or HTML Definition Term Element) identifies a term in a definition list. This element can occur only as a child element of a <dl>. It is usually followed by a <dd> element; however, multiple <dt> elements in a row indicate several terms that are all defined by the immediate next <dd> element.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:complexType mixed="true" name="dd-base">
<xs:annotation>
<xs:documentation>
The HTML <dd> Element (or HTML Description Element) indicates the description of a term in a description list (<dl>) element. This element can occur only as a child element of a definition list and it must follow a <dt> element.
</xs:documentation>
</xs:annotation>
</xs:complexType>
<xs:complexType mixed="true" name="dd-with-parts">
<xs:complexContent mixed="true">
<xs:extension base="dd-base">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-with-parts"/>
</xs:choice>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType mixed="true" name="dd-with-responses">
<xs:complexContent mixed="true">
<xs:extension base="dd-base">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-with-responses"/>
</xs:choice>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType mixed="true" name="dd-with-text">
<xs:complexContent mixed="true">
<xs:extension base="dd-base">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
</xs:choice>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="table-base">
<xs:annotation>
<xs:documentation>
The HTML Table Element (<table>) represents data in two dimensions or more.
</xs:documentation>
</xs:annotation>
<xs:attributeGroup ref="coreattrs"/>
<xs:attribute name="border" type="xs:int">
<xs:annotation>
<xs:documentation>
This integer attribute defines, in pixels, the size of the frame surrounding the table. If set to 0, it implies that the frame attribute is set to void.
Usage note: Do not use this attribute, as it has been deprecated: the <table> element should be styled using CSS. To give a similar effect than the border attribute, the CSS properties border, border-color, border-width and border-style should be used.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="cellpadding" type="xs:string">
<xs:annotation>
<xs:documentation>
This attribute defines the space between the content of a cell and the border, displayed or not, of it. If it is a pixel length, this pixel-sized space will be applied on all four sides; if it is a percentage length, the content will be centered and the total vertical space (top and bottom) will represent this percentage. The same is true for the total horizontal space (left and right).
Usage note: Do not use this attribute, as it has been deprecated: the <table> element should be styled using CSS. To give a similar effect than the border attribute, use the CSS property border-collapse with the value collapse on the <table> element itself, and the property padding on the <td>.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="cellspacing" type="xs:string">
<xs:annotation>
<xs:documentation>
This attribute defines the size, in percentage or in pixels, of the space between two cells (both horizontally and vertically), between the top of the table and the cells of the first row, the left of the table and the first column, the right of the table and the last column and the bottom of the table and the last row.
Usage note: Do not use this attribute, as it has been deprecated: the <table> element should be styled using CSS. To give a similar effect than the border attribute, use the CSS property border-collapse with the value collapse on the <table> element itself, and the property margin on the <td> element.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="TeXwidth">
<xs:annotation>
<xs:documentation>
Width of the table in %
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="[0-9]+\s*%"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="TeXtheme" type="xs:string"/>
<xs:attribute name="align" type="xs:string">
<xs:annotation>
<xs:documentation>
Deprecated attribute.
This enumerated attribute indicates how the table must be aligned in regard of the containing document. It may have the following values:
- left, meaning that the table is to be displayed to the left of the document;
- center, meaning that the table is to be displayed centered in the document;
- right, meaning that the table is to be displayed to the right of the document.
Note:
Do not use this attribute, as it has been deprecated: the <table> element should be styled using CSS. To give a similar effect than the align attribute, the CSS properties "text-align" and "vertical-align" should be used.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="rules" type="xs:string">
<xs:annotation>
<xs:documentation>
Deprecated attribute.
This enumerated attribute defines where rules, i.e. lines, should appear in a table. It can have the following values:
- none, which indicates the no rules will be displayed; it is the default value;
- groups, which will make the rules to be displayed between row groups (defined by the <thead>, <tbody> and <tfoot> elements) and between column groups (defined by the <col> and <colgroup> elements) only;
- rows, which will make the rules to be displayed between rows;
- columns, which will make the rules to be displayed between columns;
- all, which wil make the rules to be displayed between rows and columns.
Note:
The styling of the rules is browser-dependant and cannot be modified.
Do not use this attribute, as it has been deprecated: the rules should be defined and styled using CSS. use the CSS property border on the adequate <thead>, <tbody>, <tfoot>, <col> or <colgroup> elements.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:complexType name="table-with-parts">
<xs:complexContent>
<xs:extension base="table-base">
<xs:sequence>
<xs:element minOccurs="0" ref="caption"/>
<xs:element minOccurs="0" ref="thead"/>
<xs:element minOccurs="0" ref="tfoot"/>
<xs:choice>
<xs:element maxOccurs="unbounded" name="tbody" type="tbody-with-parts"/>
<xs:element maxOccurs="unbounded" name="tr" type="tr-with-parts"/>
</xs:choice>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="table-with-responses">
<xs:complexContent>
<xs:extension base="table-base">
<xs:sequence>
<xs:element minOccurs="0" ref="caption"/>
<xs:element minOccurs="0" ref="thead"/>
<xs:element minOccurs="0" ref="tfoot"/>
<xs:choice>
<xs:element maxOccurs="unbounded" name="tbody" type="tbody-with-responses"/>
<xs:element maxOccurs="unbounded" name="tr" type="tr-with-responses"/>
</xs:choice>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="table-with-text">
<xs:complexContent>
<xs:extension base="table-base">
<xs:sequence>
<xs:element minOccurs="0" ref="caption"/>
<xs:element minOccurs="0" ref="thead"/>
<xs:element minOccurs="0" ref="tfoot"/>
<xs:choice>
<xs:element maxOccurs="unbounded" name="tbody" type="tbody-with-text"/>
<xs:element maxOccurs="unbounded" name="tr" type="tr-with-text"/>
</xs:choice>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="caption">
<xs:annotation>
<xs:documentation>
The HTML <caption> Element (or HTML Table Caption Element) represents the title of a table. Though it is always the first descendant of a <table>, its styling, using CSS, may place it elsewhere, relative to the table.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:element name="thead">
<xs:annotation>
<xs:documentation>
The HTML Table Head Element (<thead>) defines a set of rows defining the head of the columns of the table.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" name="tr" type="tr-with-text"/>
</xs:sequence>
<xs:attribute name="align" type="xs:string">
<xs:annotation>
<xs:documentation>
Deprecated attribute.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="tfoot">
<xs:annotation>
<xs:documentation>
The HTML Table Foot Element (<tfoot>) defines a set of rows summarizing the columns of the table.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" name="tr" type="tr-with-text"/>
</xs:sequence>
<xs:attribute name="align" type="xs:string">
<xs:annotation>
<xs:documentation>
Deprecated attribute.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:complexType name="tbody-with-parts">
<xs:sequence>
<xs:choice>
<xs:element maxOccurs="unbounded" name="tr" type="tr-with-parts"/>
</xs:choice>
</xs:sequence>
<xs:attribute name="align" type="xs:string">
<xs:annotation>
<xs:documentation>
Deprecated attribute.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:complexType name="tbody-with-responses">
<xs:sequence>
<xs:choice>
<xs:element maxOccurs="unbounded" name="tr" type="tr-with-responses"/>
</xs:choice>
</xs:sequence>
<xs:attribute name="align" type="xs:string"/>
</xs:complexType>
<xs:complexType name="tbody-with-text">
<xs:sequence>
<xs:choice>
<xs:element maxOccurs="unbounded" name="tr" type="tr-with-text"/>
</xs:choice>
</xs:sequence>
<xs:attribute name="align" type="xs:string">
<xs:annotation>
<xs:documentation>
Deprecated attribute.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:complexType name="tr-base">
<xs:annotation>
<xs:documentation>
Table row
</xs:documentation>
</xs:annotation>
<xs:attributeGroup ref="coreattrs"/>
<xs:attribute name="align" type="xs:string">
<xs:annotation>
<xs:documentation>
Deprecated attribute.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:complexType name="tr-with-parts">
<xs:complexContent>
<xs:extension base="tr-base">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="th"/>
<xs:element name="td" type="td-with-parts"/>
</xs:choice>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="tr-with-responses">
<xs:complexContent>
<xs:extension base="tr-base">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="th"/>
<xs:element name="td" type="td-with-responses"/>
</xs:choice>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="tr-with-text">
<xs:complexContent>
<xs:extension base="tr-base">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="th"/>
<xs:element name="td" type="td-with-text"/>
</xs:choice>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:simpleType name="html-align">
<xs:restriction base="xs:string">
<xs:enumeration value="left"/>
<xs:enumeration value="center"/>
<xs:enumeration value="right"/>
<xs:enumeration value="justify"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType mixed="true" name="td-base">
<xs:annotation>
<xs:documentation>
Table cell
</xs:documentation>
</xs:annotation>
<xs:attributeGroup ref="coreattrs"/>
<xs:attribute name="colspan" type="xs:int">
<xs:annotation>
<xs:documentation>
This attribute contains a non-negative integer value that indicates on how many columns does the cell extend. Its default value is 1.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="rowspan" type="xs:int">
<xs:annotation>
<xs:documentation>
This attribute contains a non-negative integer value that indicates on how many rows does the cell extend. Its default value is 1.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="align" type="html-align"/>
<xs:attribute name="TeXwidth">
<xs:annotation>
<xs:documentation>
Width of the cell in mm or another unit (cm, in, pt, pc)
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="\d*(\s+(mm|cm|in|pt|pc))?"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
<xs:complexType mixed="true" name="td-with-parts">
<xs:complexContent mixed="true">
<xs:extension base="td-base">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-with-parts"/>
</xs:choice>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType mixed="true" name="td-with-responses">
<xs:complexContent mixed="true">
<xs:extension base="td-base">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-with-responses"/>
</xs:choice>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType mixed="true" name="td-with-text">
<xs:complexContent mixed="true">
<xs:extension base="td-base">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
</xs:choice>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="th">
<xs:annotation>
<xs:documentation>
Table header cell
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
</xs:choice>
<xs:attributeGroup ref="coreattrs"/>
<xs:attribute name="colspan" type="xs:int">
<xs:annotation>
<xs:documentation>
This attribute contains a non-negative integer value that indicates on how many columns does the cell extend. Its default value is 1.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="rowspan" type="xs:int">
<xs:annotation>
<xs:documentation>
This attribute contains a non-negative integer value that indicates on how many rows does the cell extend. Its default value is 1.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="align" type="html-align"/>
<xs:attribute name="scope">
<xs:annotation>
<xs:documentation>
defines the cells that the header defined in this <th> element relates to
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="row"/>
<xs:enumeration value="col"/>
<xs:enumeration value="rowgroup"/>
<xs:enumeration value="colgroup"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="TeXwidth">
<xs:annotation>
<xs:documentation>
Width of the cell in mm or another unit (cm, in, pt, pc)
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="\d*(\s+(mm|cm|in|pt|pc))?"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="span">
<xs:annotation>
<xs:documentation>
Inline style
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="inlines"/>
</xs:choice>
<xs:attributeGroup ref="coreattrs"/>
</xs:complexType>
</xs:element>
<xs:element name="a">
<xs:annotation>
<xs:documentation>
The HTML <a> Element (or the HTML Anchor Element) defines a hyperlink, the named target destination for a hyperlink, or both.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="inlines"/>
</xs:choice>
<xs:attributeGroup ref="coreattrs"/>
<xs:attribute name="name" type="xs:string"/>
<xs:attribute name="href" type="xs:anyURI"/>
<xs:attribute name="target" type="xs:string"/>
<xs:attribute name="title" type="xs:string"/>
<xs:attribute name="uriprint" type="xs:string"/>
<xs:attribute name="anchorprint" type="xs:string"/>
<xs:attribute name="rel" type="xs:NMTOKENS"/>
<xs:attribute name="accesskey">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:length fixed="true" value="1"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="onclick" type="xs:string">
<xs:annotation>
<xs:documentation>
Javascript event handler content attribute for the "click" event.
Warning: event handler content attributes should be avoided. They make the markup bigger and less readable. Concerns of content/structure and behavior are not well-separated, making a bug harder to find. Furthermore, usage of event attributes almost always causes scripts to expose global functions on the Window object, polluting the global namespace.
The EventTarget.addEventListener() function should be used instead to add a listener for the event.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="em" type="inlineBaseType">
<xs:annotation>
<xs:documentation>
Emphasis
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="strong" type="inlineBaseType">
<xs:annotation>
<xs:documentation>
Strong emphasis
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="b" type="inlineBaseType">
<xs:annotation>
<xs:documentation>
Bold
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="i" type="inlineBaseType">
<xs:annotation>
<xs:documentation>
Italic
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="sup" type="inlineBaseType">
<xs:annotation>
<xs:documentation>
Superscript
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="sub" type="inlineBaseType">
<xs:annotation>
<xs:documentation>
Subscript
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="pre" type="inlineBaseType">
<xs:annotation>
<xs:documentation>
The HTML <pre> Element (or HTML Preformatted Text) represents preformatted text. Text within this element is typically displayed in a non-proportional font exactly as it is laid out in the file. Whitespaces inside this element are displayed as typed.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="code" type="inlineBaseType">
<xs:annotation>
<xs:documentation>
The HTML <code> Element represents a fragment of computer code. By default, it is displayed in the browser's default monospace font.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="kbd" type="inlineBaseType">
<xs:annotation>
<xs:documentation>
The HTML <kbd> Element (or HTML Keyboard Input Element) represents user input and produces an inline element displayed in the browser's default monotype font.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="samp" type="inlineBaseType">
<xs:annotation>
<xs:documentation>
The HTML <samp> element is an element intended to identify sample output from a computer program. It is usually displayed in the browser's default monotype font.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="cite" type="inlineBaseType">
<xs:annotation>
<xs:documentation>
The HTML <cite> Element (or HTML Citation Element) represents a reference to a creative work. It must include the title of a work, the name of the author, or a URL reference, which may be in an abbreviated form according to the conventions used for the addition of citation metadata.
Usage Notes:
A creative work may include a book, a paper, an essay, a poem, a score, a song, a script, a film, a TV show, a game, a sculpture, a painting, a theater production, a play, an opera, a musical, an exhibition, a legal case report, a computer program, , a web site, a web page, a blog post or comment, a forum post or comment, a tweet, a written or oral statement, etc.
Use the cite attribute on a <blockquote> or <q> element to reference an online resource for a source.
Style note:
To avoid the default italic style from being used for the <cite> element use the CSS font-style property.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="q">
<xs:annotation>
<xs:documentation>
The HTML <q> Element (or HTML Quote Element) indicates that the enclosed text is a short inline quotation. This element is intended for short quotations that don't require paragraph breaks; for long quotations use <blockquote> element.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="inlines"/>
</xs:choice>
<xs:attribute name="cite" type="xs:anyURI">
<xs:annotation>
<xs:documentation>
The value of this attribute is a URL that designates a source document or message for the information quoted. This attribute is intended to point to information explaining the context or the reference for the quote.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="tt" type="inlineBaseType">
<xs:annotation>
<xs:documentation>
This feature is obsolete. Although it may still work in some browsers, its use is discouraged since it could be removed at any time. Try to avoid using it.
The HTML Teletype Text Element (<tt>) produces an inline element displayed in the browser's default monotype font. This element was intended to style text as it would display on a fixed width display, such as a teletype. It probably is more common to display fixed width type using the <code> element.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="ins">
<xs:annotation>
<xs:documentation>
The HTML <ins> Element (or HTML Inserted Text) HTML represents a range of text that has been added to a document.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:complexContent>
<xs:extension base="inlineBaseType">
<xs:attribute name="cite" type="xs:anyURI"/>
<xs:attribute name="datetime" type="xs:dateTime"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:element name="del">
<xs:annotation>
<xs:documentation>
The HTML <del> element (or HTML Deleted Text Element) represents a range of text that has been deleted from a document. This element is often (but need not be) rendered with strike-through text.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:complexContent>
<xs:extension base="inlineBaseType">
<xs:attribute name="cite" type="xs:anyURI"/>
<xs:attribute name="datetime" type="xs:dateTime"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:element name="var" type="inlineBaseType">
<xs:annotation>
<xs:documentation>
The HTML Variable Element (<var>) represents a variable in a mathematical expression or a programming context.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="small" type="inlineBaseType">
<xs:annotation>
<xs:documentation>
The HTML Small Element (<small>) makes the text font size one size smaller (for example, from large to medium, or from small to x-small) down to the browser's minimum font size. In HTML5, this element is repurposed to represent side-comments and small print, including copyright and legal text, independent of its styled presentation.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="big" type="inlineBaseType">
<xs:annotation>
<xs:documentation>
This feature is obsolete. Although it may still work in some browsers, its use is discouraged since it could be removed at any time. Try to avoid using it.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="br">
<xs:annotation>
<xs:documentation>
The HTML <br> Element (or HTML Line Break Element) produces a line break in text (carriage-return). It is useful for writing a poem or an address, where the division of lines is significant.
</xs:documentation>
</xs:annotation>
<xs:complexType>
</xs:complexType>
</xs:element>
<xs:element name="hr">
<xs:annotation>
<xs:documentation>
The HTML <hr> element represents a thematic break between paragraph-level elements (for example, a change of scene in a story, or a shift of topic with a section). In previous versions of HTML, it represented a horizontal rule. It may still be displayed as a horizontal rule in visual browsers, but is now defined in semantic terms, rather than presentational terms.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attributeGroup ref="coreattrs"/>
</xs:complexType>
</xs:element>
<xs:element name="address">
<xs:annotation>
<xs:documentation>
The HTML <address> Element may be used by authors to supply contact information for its nearest <article> or <body> ancestor; in the latter case, it applies to the whole document.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
</xs:choice>
<xs:attributeGroup ref="coreattrs"/>
</xs:complexType>
</xs:element>
<xs:element name="blockquote">
<xs:annotation>
<xs:documentation>
The HTML <blockquote> Element (or HTML Block Quotation Element) indicates that the enclosed text is an extended quotation. Usually, this is rendered visually by indentation (to change <blockquote> indent, use CSS margin property). A URL for the source of the quotation may be given using the cite attribute, while a text representation of the source can be given using the <cite> element.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
</xs:choice>
<xs:attributeGroup ref="coreattrs"/>
<xs:attribute name="cite" type="xs:anyURI"/>
<xs:attribute name="align">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="center"/>
<xs:enumeration value="left"/>
<xs:enumeration value="right"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:simpleType name="htmlLength">
<xs:annotation>
<xs:documentation>
nn for pixels or nn% for percentage length
</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:string">
<xs:pattern value="[\-+]?(\d+|\d+(\.\d+)?%)"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="htmlLength-or-perl">
<xs:union memberTypes="htmlLength perl"/>
</xs:simpleType>
<xs:element name="img">
<xs:annotation>
<xs:documentation>
The HTML <img> Element (or HTML Image Element) represents an image of the document.
Usage note:
Browsers do not always display the image referenced by the element. This is the case for non-graphical browsers (including those used by people with vision impairments), or if the user chooses not to display images, or if the browser is unable to display the image because it is invalid or an unsupported type. In these cases, the browser may replace the image with the text defined in this element's alt attribute.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attributeGroup ref="coreattrs"/>
<xs:attribute name="src" type="xs:anyURI" use="required">
<xs:annotation>
<xs:documentation>
Image URL.
On browsers supporting srcset, src is ignored if this one is provided.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="alt" type="xs:string">
<xs:annotation>
<xs:documentation>
This attribute defines the alternative text describing the image. Users will see this displayed if the image URL is wrong, the image is not in one of the supported formats, or until the image is downloaded.
Usage note: Omitting this attribute indicates that the image is a key part of the content, but no textual equivalent is available. Setting this attribute to the empty string indicates that this image is not a key part of the content; non-visual browsers may omit it from rendering.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="width" type="htmlLength-or-perl">
<xs:annotation>
<xs:documentation>
The width of the image in pixels or percent.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="height" type="htmlLength-or-perl">
<xs:annotation>
<xs:documentation>
The height of the image in pixels or percent.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="TeXwidth">
<xs:annotation>
<xs:documentation>
Allows you to set the width of the image, in mm or %, as it will be rendered into the LaTeX document used to print the problem.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="[0-9]+(\.[0-9]+)?(\s*%)?"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="TeXheight" type="decimal-or-perl">
<xs:annotation>
<xs:documentation>
Allows you to set the height of the image, in mm, as it will be rendered into the LaTeX document used to print the problem.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="align">
<xs:annotation>
<xs:documentation>
This attribute is deprecated since HTML 4.01 and obsolete since HTML5. Use the vertical-align CSS property instead.
Specifies the alignment of the image relative to the enclosing text paragraph:
- bottom: The image will be aligned so that its bottom will be at the baseline of the surrounding text.
- middle: The image will be aligned so that its center-line will be at the baseline of the surrounding text.
- top: The image will be aligned so that its top will be at the baseline of the surrounding text.
- left: The image will be placed so that it is at the left of the surrounding text. The surrounding text will fill in the region to the right of the image.
- right: The image will be placed so that it is at the right of the surrounding text. The surrounding text will fill in the region to the left of the image.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="bottom"/>
<xs:enumeration value="middle"/>
<xs:enumeration value="top"/>
<xs:enumeration value="left"/>
<xs:enumeration value="right"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="TeXwrap">
<xs:annotation>
<xs:documentation>
Allows you to select how the LaTeX document will attempt to wrap text around a horizontally aligned image.
parbox: \newline and \parbox will be used to place the image. This method ensures that text will not be wrapped on top of the image, however very little text will appear next to the image itself.
parpic: The picins package \parpic command will be used to place the image. This will wrap the remainder of the paragraph containing the picture around the image.
If, however, there is insufficient text to fill the space to the left or right of the image, the next paragraph may be wrapped on top of the image. In addition, \parpic does not always honor the end of the page, causing the image to extend below the page footer.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="perl">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="none"/>
<xs:enumeration value="parbox"/>
<xs:enumeration value="parpic"/>
<xs:enumeration value="wrapfigure"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute default="no" name="encrypturl" type="yesno-or-perl"/>
</xs:complexType>
</xs:element>
<xs:element name="figure">
<xs:annotation>
<xs:documentation>
The HTML <figure> Element represents self-contained content, frequently with a caption (<figcaption>), and is typically referenced as a single unit. While it is related to the main flow, its position is independent of the main flow. Usually this is an image, an illustration, a diagram, a code snippet, or a schema that is referenced in the main text, but that can be moved to another page or to an appendix without affecting the main flow.
Usage note: A caption can be associated with the <figure> element by inserting a <figcaption> inside it (as the first or the last child).
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice minOccurs="0">
<xs:sequence>
<xs:element ref="figcaption"/>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
</xs:choice>
</xs:sequence>
<xs:sequence>
<xs:choice maxOccurs="unbounded">
<xs:group ref="text-only"/>
</xs:choice>
<xs:element minOccurs="0" ref="figcaption"/>
</xs:sequence>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:element name="figcaption">
<xs:annotation>
<xs:documentation>
The HTML <figcaption> Element represents a caption or a legend associated with a figure or an illustration described by the rest of the data of the <figure> element which is its immediate ancestor which means <figcaption> can be the first or last element inside a <figure> block. Also, the HTML Figcaption Element is optional; if not provided, then the parent figure element will have no caption.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:element name="object">
<xs:annotation>
<xs:documentation>
The HTML <object> Element (or HTML Embedded Object Element) represents an external resource, which can be treated as an image, a nested browsing context, or a resource to be handled by a plugin.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="param"/>
<xs:group ref="text-only"/>
</xs:choice>
<xs:attributeGroup ref="coreattrs"/>
<xs:attribute name="classid" type="xs:string">
<xs:annotation>
<xs:documentation>
The URI of the object's implementation. It can be used together with, or in place of, the data attribute.
Obsolete since HTML5.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="codebase" type="xs:anyURI">
<xs:annotation>
<xs:documentation>
The base path used to resolve relative URIs specified by classid, data, or archive. If not specified, the default is the base URI of the current document.
Obsolete since HTML5.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="data" type="xs:anyURI">
<xs:annotation>
<xs:documentation>
The address of the resource as a valid URL. At least one of data and type must be defined.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="type" type="xs:string">
<xs:annotation>
<xs:documentation>
The content type of the resource specified by data. At least one of data and type must be defined.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="codetype" type="xs:string">
<xs:annotation>
<xs:documentation>
The content type of the data specified by classid.
Obsolete since HTML5.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="archive">
<xs:annotation>
<xs:documentation>
A space-separated list of URIs for archives of resources for the object.
Obsolete since HTML5.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:list itemType="xs:anyURI"/>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="standby" type="xs:string">
<xs:annotation>
<xs:documentation>
A message that the browser can show while loading the object's implementation and data.
Obsolete since HTML5.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="width" type="htmlLength-or-perl">
<xs:annotation>
<xs:documentation>
The width of the display resource, in CSS pixels.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="height" type="htmlLength-or-perl">
<xs:annotation>
<xs:documentation>
The height of the displayed resource, in CSS pixels.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="usemap" type="xs:string">
<xs:annotation>
<xs:documentation>
A hash-name reference to a <map> element; that is a '#' followed by the value of a name of a map element.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="name" type="xs:NMTOKEN">
<xs:annotation>
<xs:documentation>
The name of valid browsing context (HTML5), or the name of the control (HTML 4).
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="param">
<xs:annotation>
<xs:documentation>
param is used to supply a named property value
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute name="id" type="xs:ID"/>
<xs:attribute name="name">
<xs:annotation>
<xs:documentation>
Name of the parameter.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="value">
<xs:annotation>
<xs:documentation>
Specifies the value of the parameter.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="data" name="valuetype">
<xs:annotation>
<xs:documentation>
Obsolete in HTML5.
Specifies the type of the value attribute.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="data"/>
<xs:enumeration value="ref"/>
<xs:enumeration value="object"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="type" type="xs:string">
<xs:annotation>
<xs:documentation>
Obsolete in HTML5.
Only used if the valuetype is set to "ref". Specifies the MIME type of values found at the URI specified by value.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="embed">
<xs:annotation>
<xs:documentation>
The HTML <embed> Element represents an integration point for an external application or interactive content (in other words, a plug-in).
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute name="id" type="xs:ID"/>
<xs:attribute name="src" type="xs:anyURI">
<xs:annotation>
<xs:documentation>
The URL of the resource being embedded.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="type" type="xs:string">
<xs:annotation>
<xs:documentation>
The MIME type to use to select the plug-in to instantiate.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="width" type="non-negative-int-or-perl">
<xs:annotation>
<xs:documentation>
The displayed width of the resource, in CSS pixels.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="height" type="non-negative-int-or-perl">
<xs:annotation>
<xs:documentation>
The displayed height of the resource, in CSS pixels.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="applet">
<xs:annotation>
<xs:documentation>
This feature is obsolete. Although it may still work in some browsers, its use is discouraged since it could be removed at any time. Try to avoid using it.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="param"/>
<xs:group ref="text-only"/>
</xs:choice>
<xs:attribute name="id" type="xs:ID"/>
<xs:attribute name="codebase" type="xs:anyURI"/>
<xs:attribute name="archive" type="xs:string">
<xs:annotation>
<xs:documentation>
Comma-separated list of URIs for archives containing classes and other resources that will be "preloaded".
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="code"/>
<xs:attribute name="object"/>
<xs:attribute name="alt" type="xs:string"/>
<xs:attribute name="name" type="xs:NMTOKEN"/>
<xs:attribute name="width" type="non-negative-int-or-perl" use="required"/>
<xs:attribute name="height" type="non-negative-int-or-perl" use="required"/>
<xs:attribute name="align">
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="top"/>
<xs:enumeration value="middle"/>
<xs:enumeration value="bottom"/>
<xs:enumeration value="left"/>
<xs:enumeration value="right"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="hspace" type="xs:nonNegativeInteger"/>
<xs:attribute name="vspace" type="xs:nonNegativeInteger"/>
</xs:complexType>
</xs:element>
<xs:element name="video">
<xs:annotation>
<xs:documentation>
The HTML <video> element is used to embed video content. It may contain several video sources, represented using the src attribute or the <source> element; the browser will choose the most suitable one.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="source"/>
</xs:choice>
<xs:attributeGroup ref="coreattrs"/>
<xs:attribute name="src" type="xs:anyURI"/>
<xs:attribute name="width" type="non-negative-int-or-perl"/>
<xs:attribute name="height" type="non-negative-int-or-perl"/>
<xs:attribute name="autoplay">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="autoplay"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="controls">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="controls"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="loop">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="loop"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="source">
<xs:annotation>
<xs:documentation>
The HTML <source> element is used to specify multiple media resources for <picture>, <audio> and <video> elements. It is an empty element. It is commonly used to serve the same media in multiple formats supported by different browsers.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute name="src" type="xs:anyURI" use="required"/>
<xs:attribute name="type" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:element name="audio">
<xs:annotation>
<xs:documentation>
The HTML <audio> element is used to embed sound content in documents. It may contain several audio sources, represented using the src attribute or the <source> element; the browser will choose the most suitable one.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element ref="source"/>
</xs:choice>
<xs:attributeGroup ref="coreattrs"/>
<xs:attribute name="src" type="xs:anyURI"/>
<xs:attribute name="autoplay">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="autoplay"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="controls">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="controls"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="loop">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="loop"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="map">
<xs:annotation>
<xs:documentation>
The HTML <map> element is used with <area> elements to define an image map (a clickable link area).
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:choice>
<xs:choice maxOccurs="unbounded">
<xs:group ref="blocks-with-text"/>
</xs:choice>
<xs:element maxOccurs="unbounded" ref="area"/>
</xs:choice>
<xs:attribute name="id" type="xs:ID" use="required"/>
<xs:attribute name="class" type="xs:NMTOKENS"/>
<xs:attribute name="style" type="xs:string"/>
<xs:attribute name="title" type="xs:string"/>
<xs:attribute name="name" type="xs:NMTOKEN"/>
</xs:complexType>
</xs:element>
<xs:element name="area">
<xs:annotation>
<xs:documentation>
The HTML <area> element defines a hot-spot region on an image, and optionally associates it with a hypertext link. This element is used only within a <map> element.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute default="rect" name="shape">
<xs:annotation>
<xs:documentation>
The shape of the associated hot spot. The specifications for HTML 5 and HTML 4 define the values rect, which defines a rectangular region; circle, which defines a circular region; poly, which defines a polygon; and default, which indicates the entire region beyond any defined shapes.
Many browsers, notably Internet Explorer 4 and higher, support circ, polygon, and rectangle as valid values for shape; these values are *not standard*.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="rect"/>
<xs:enumeration value="circle"/>
<xs:enumeration value="poly"/>
<xs:enumeration value="default"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="coords">
<xs:annotation>
<xs:documentation>
A set of values specifying the coordinates of the hot-spot region. The number and meaning of the values depend upon the value specified for the shape attribute. For a rect or rectangle shape, the coords value is two x,y pairs: left, top, right, and bottom. For a circle shape, the value is x,y,r where x,y is a pair specifying the center of the circle and r is a value for the radius. For a poly or polygon< shape, the value is a set of x,y pairs for each point in the polygon: x1,y1,x2,y2,x3,y3, and so on. In HTML4, the values are numbers of pixels or percentages, if a percent sign (%) is appended; in HTML5, the values are numbers of CSS pixels.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="[\-+]?(\d+|\d+(\.\d+)?%)(,\s*[\-+]?(\d+|\d+(\.\d+)?%))*"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="href" type="xs:anyURI">
<xs:annotation>
<xs:documentation>
The hyperlink target for the area. Its value is a valid URL. In HTML4, either this attribute or the nohref attribute must be present in the element. In HTML5, this attribute may be omitted; if so, the area element does not represent a hyperlink.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="nohref">
<xs:annotation>
<xs:documentation>
Indicates that no hyperlink exists for the associated area. Either this attribute or the href attribute must be present in the element.
Usage note: This attribute is obsolete in HTML5, instead omitting the href attribute is sufficient.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="nohref"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="alt" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>
A text string alternative to display on browsers that do not display images. The text should be phrased so that it presents the user with the same kind of choice as the image would offer when displayed without the alternative text. In HTML4, this attribute is required, but may be the empty string (""). In HTML5, this attribute is required only if the href attribute is used.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="canvas">
<xs:annotation>
<xs:documentation>
The HTML <canvas> Element can be used to draw graphics via scripting (usually JavaScript). For example, it can be used to draw graphs, make photo compositions or even perform animations. You may (and should) provide alternate content inside the <canvas> block. That content will be rendered both on older browsers that don't support canvas and in browsers with JavaScript disabled.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="blocks-with-text"/>
<xs:group ref="inlines"/>
</xs:choice>
<xs:attributeGroup ref="coreattrs"/>
<xs:attribute default="300" name="width" type="htmlLength-or-perl">
<xs:annotation>
<xs:documentation>
The width of the coordinate space in CSS pixels.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="150" name="height" type="htmlLength-or-perl">
<xs:annotation>
<xs:documentation>
The height of the coordinate space in CSS pixels.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="form">
<xs:annotation>
<xs:documentation>
The HTML <form> element represents a document section that contains interactive controls to submit information to a web server.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="blocks-with-text"/>
</xs:choice>
<xs:attributeGroup ref="coreattrs"/>
<xs:attribute name="action" type="xs:anyURI">
<xs:annotation>
<xs:documentation>
The URI of a program that processes the form information.
In HTML5, the action attribute is no longer required.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute default="get" name="method">
<xs:annotation>
<xs:documentation>
The HTTP method that the browser uses to submit the form. Possible values are:
- post: Corresponds to the HTTP POST method ; form data are included in the body of the form and sent to the server.
- get: Corresponds to the HTTP GET method; form data are appended to the action attribute URI with a '?' as separator, and the resulting URI is sent to the server. Use this method when the form has no side-effects and contains only ASCII characters.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="get"/>
<xs:enumeration value="post"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute default="application/x-www-form-urlencoded" name="enctype" type="xs:string">
<xs:annotation>
<xs:documentation>
When the value of the method attribute is post, enctype is the MIME type of content that is used to submit the form to the server. Possible values are:
- application/x-www-form-urlencoded: The default value if the attribute is not specified.
- multipart/form-data: The value used for an <input> element with the type attribute set to "file".
- text/plain (HTML5)
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="accept-charset" type="xs:string">
<xs:annotation>
<xs:documentation>
A space- or comma-delimited list of character encodings that the server accepts. The browser uses them in the order in which they are listed. The default value, the reserved string "UNKNOWN", indicates the same encoding as that of the document containing the form element.
In previous versions of HTML, the different character encodings could be delimited by spaces or commas. In HTML5, only spaces are allowed as delimiters.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="name" type="xs:string">
<xs:annotation>
<xs:documentation>
The name of the form. In HTML 4, its use is deprecated (id should be used instead). It must be unique among the forms in a document and not just an empty string in HTML 5.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="accept" type="xs:string">
<xs:annotation>
<xs:documentation>
A comma-separated list of content types that the server accepts.
Usage note: This attribute has been removed in HTML5 and should no longer be used. Instead, use the accept attribute of the specific <input> element.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="onsubmit" type="xs:string"/>
<xs:attribute name="onreset" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:element name="label">
<xs:annotation>
<xs:documentation>
The HTML <label> Element represents a caption for an item in a user interface. It can be associated with a control either by placing the control element inside the label element, or by using the for attribute. Such a control is called the labeled control of the label element.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:complexContent>
<xs:extension base="inlineBaseType">
<xs:attribute name="for" type="xs:IDREF"/>
<xs:attribute name="accesskey">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:length fixed="true" value="1"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="onfocus" type="xs:string"/>
<xs:attribute name="onblur" type="xs:string"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:simpleType name="InputType">
<xs:restriction base="xs:token">
<xs:enumeration value="text"/>
<xs:enumeration value="password"/>
<xs:enumeration value="checkbox"/>
<xs:enumeration value="radio"/>
<xs:enumeration value="submit"/>
<xs:enumeration value="reset"/>
<xs:enumeration value="file"/>
<xs:enumeration value="hidden"/>
<xs:enumeration value="image"/>
<xs:enumeration value="button"/>
</xs:restriction>
</xs:simpleType>
<xs:element name="input">
<xs:annotation>
<xs:documentation>
The HTML <input> element is used to create interactive controls for web-based forms in order to accept data from user.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attributeGroup ref="coreattrs"/>
<xs:attribute default="text" name="type" type="InputType"/>
<xs:attribute name="name">
<xs:annotation>
<xs:documentation>
the name attribute is required for all but submit & reset
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="value"/>
<xs:attribute name="checked">
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="checked"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="disabled">
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="disabled"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="readonly">
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="readonly"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="size"/>
<xs:attribute name="maxlength" type="xs:nonNegativeInteger"/>
<xs:attribute name="src" type="xs:anyURI"/>
<xs:attribute name="alt"/>
<xs:attribute name="usemap" type="xs:anyURI"/>
<xs:attribute name="onselect" type="xs:string"/>
<xs:attribute name="onchange" type="xs:string"/>
<xs:attribute name="accept" type="xs:string"/>
<xs:attribute name="onclick" type="xs:string">
<xs:annotation>
<xs:documentation>
Javascript event handler content attribute for the "click" event.
Warning: event handler content attributes should be avoided. They make the markup bigger and less readable. Concerns of content/structure and behavior are not well-separated, making a bug harder to find. Furthermore, usage of event attributes almost always causes scripts to expose global functions on the Window object, polluting the global namespace.
The EventTarget.addEventListener() function should be used instead to add a listener for the event.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="select">
<xs:annotation>
<xs:documentation>
The HTML select (<select>) element represents a control that presents a menu of options. The options within the menu are represented by <option> elements, which can be grouped by <optgroup> elements. Options can be pre-selected for the user.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:choice maxOccurs="unbounded">
<xs:element ref="optgroup"/>
<xs:element ref="option"/>
</xs:choice>
<xs:attribute name="name"/>
<xs:attribute name="size" type="xs:nonNegativeInteger"/>
<xs:attribute name="multiple">
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="multiple"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="disabled">
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="disabled"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="tabindex" type="xs:nonNegativeInteger"/>
<xs:attribute name="onfocus" type="xs:string"/>
<xs:attribute name="onblur" type="xs:string"/>
<xs:attribute name="onchange" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:element name="optgroup">
<xs:annotation>
<xs:documentation>
In a Web form, the HTML <optgroup> element creates a grouping of options within a <select> element.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" ref="option"/>
</xs:sequence>
<xs:attribute name="disabled">
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="disabled"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="label" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="option">
<xs:annotation>
<xs:documentation>
In a Web form, the HTML <option> element is used to create a control representing an item within a <select>, an <optgroup> or a <datalist> HTML5 element.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:attribute name="selected">
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="selected"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="disabled">
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="disabled"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="label" type="xs:string"/>
<xs:attribute name="value"/>
</xs:complexType>
</xs:element>
<xs:element name="textarea">
<xs:annotation>
<xs:documentation>
The HTML <textarea> element represents a multi-line plain-text editing control.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:attribute name="name"/>
<xs:attribute name="rows" type="xs:nonNegativeInteger" use="required"/>
<xs:attribute name="cols" type="xs:nonNegativeInteger" use="required"/>
<xs:attribute name="disabled">
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="disabled"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="readonly">
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="readonly"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="onselect" type="xs:string"/>
<xs:attribute name="onchange" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:element name="fieldset">
<xs:annotation>
<xs:documentation>
The HTML <fieldset> element is used to group several controls as well as labels (<label>) within a web form.
Only one legend element should occur in the content, and if present should only be preceded by whitespace.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:sequence>
<xs:element ref="legend"/>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
</xs:choice>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="legend">
<xs:annotation>
<xs:documentation>
The HTML <legend> Element (or HTML Legend Field Element) represents a caption for the content of its parent <fieldset>.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:complexContent>
<xs:extension base="inlineBaseType">
<xs:attribute name="accesskey">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:length fixed="true" value="1"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:element name="button">
<xs:annotation>
<xs:documentation>
The HTML <button> Element represents a clickable button.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:group ref="text-only"/>
</xs:choice>
<xs:attribute name="name"/>
<xs:attribute name="value"/>
<xs:attribute default="submit" name="type">
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="button"/>
<xs:enumeration value="submit"/>
<xs:enumeration value="reset"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="disabled">
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="disabled"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="iframe">
<xs:annotation>
<xs:documentation>
The HTML <iframe> Element (or HTML inline frame element) represents a nested browsing context, effectively embedding another HTML page into the current page.
</xs:documentation>
</xs:annotation>
<xs:complexType mixed="true">
<xs:attributeGroup ref="coreattrs"/>
<xs:attribute name="name" type="xs:NMTOKEN"/>
<xs:attribute name="src" type="xs:anyURI"/>
<xs:attribute default="1" name="frameborder">
<xs:annotation>
<xs:documentation>
Warning: HTML 4 only
The value 1 (the default) tells the browser to draw a border between this frame and every other frame.
The value 0 tells the browser not to draw a border between this frame and other frames.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="1"/>
<xs:enumeration value="0"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="height" type="htmlLength"/>
<xs:attribute name="width" type="htmlLength"/>
<xs:attribute name="allowfullscreen">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="allowfullscreen"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>
Index: modules/damieng/clean_xml/post_xml.pm
+++ modules/damieng/clean_xml/post_xml.pm
#!/usr/bin/perl
package post_xml;
use strict;
use utf8;
use warnings;
use File::Basename;
use File::Temp qw/ tempfile /;
use Cwd 'abs_path';
use XML::LibXML;
use HTML::TokeParser; # used to parse sty files
use Tie::IxHash; # for ordered hashes
use Env qw(RES_DIR); # path of res directory parent (without the / at the end)
no warnings 'recursion'; # yes, fix_paragraph is using heavy recursion, I know
# these are constants
my @block_elements = ('parameter','location','answer','foil','image','polygon','rectangle','text','conceptgroup','itemgroup','item','label','data','function','array','unit','answergroup','functionplotresponse','functionplotruleset','functionplotelements','functionplotcustomrule','essayresponse','hintpart','formulahint','numericalhint','reactionhint','organichint','optionhint','radiobuttonhint','stringhint','customhint','mathhint','formulahintcondition','numericalhintcondition','reactionhintcondition','organichintcondition','optionhintcondition','radiobuttonhintcondition','stringhintcondition','customhintcondition','mathhintcondition','imageresponse','foilgroup','datasubmission','textfield','hiddensubmission','radiobuttonresponse','rankresponse','matchresponse','import','style','script','window','block','library','notsolved','part','postanswerdate','preduedate','problem','problemtype','randomlabel','bgimg','labelgroup','randomlist','solved','while','tex','print','web','gnuplo!
t','curve','Task','IntroParagraph','ClosingParagraph','Question','QuestionText','Setup','Instance','InstanceText','Criteria','CriteriaText','GraderNote','languageblock','translated','lang','instructorcomment','dataresponse','togglebox','standalone','comment','drawimage','allow','displayduedate','displaytitle','responseparam','organicstructure','scriptlib','parserlib','drawoptionlist','spline','backgroundplot','plotobject','plotvector','drawvectorsum','functionplotrule','functionplotvectorrule','functionplotvectorsumrule','axis','key','xtics','ytics','title','xlabel','ylabel','hiddenline','dtm');
my @inline_like_block = ('stringresponse','optionresponse','numericalresponse','formularesponse','mathresponse','organicresponse','reactionresponse','customresponse','externalresponse', 'hint', 'hintgroup'); # inline elements treated like blocks for pretty print and some other things
my @responses = ('stringresponse','optionresponse','numericalresponse','formularesponse','mathresponse','organicresponse','reactionresponse','customresponse','externalresponse','essayresponse','radiobuttonresponse','matchresponse','rankresponse','imageresponse','functionplotresponse');
my @block_html = ('html','head','body','section','h1','h2','h3','h4','h5','h6','div','p','ul','ol','li','table','tbody','tr','td','th','dl','dt','dd','pre','noscript','hr','address','blockquote','object','applet','embed','map','form','fieldset','iframe','center','frameset');
my @no_newline_inside = ('import','parserlib','scriptlib','data','function','label','xlabel','ylabel','tic','text','rectangle','image','title','h1','h2','h3','h4','h5','h6','li','td','p');
my @preserve_elements = ('script','answer','pre');
my @accepting_style = ('section','h1','h2','h3','h4','h5','h6','div','p','li','td','th','dt','dd','pre','blockquote');
my @latex_math = ('\alpha', '\theta', '\omicron', '\tau', '\beta', '\vartheta', '\pi', '\upsilon', '\gamma', '\gamma', '\varpi', '\phi', '\delta', '\kappa', '\rho', '\varphi', '\epsilon', '\lambda', '\varrho', '\chi', '\varepsilon', '\mu', '\sigma', '\psi', '\zeta', '\nu', '\varsigma', '\omega', '\eta', '\xi',
'\Gamma', '\Lambda', '\Sigma', '\Psi', '\Delta', '\Xi', '\Upsilon', '\Omega', '\Theta', '\Pi', '\Phi',
'\pm', '\cap', '\diamond', '\oplus', '\mp', '\cup', '\bigtriangleup', '\ominus', '\times', '\uplus', '\bigtriangledown', '\otimes', '\div', '\sqcap', '\triangleleft', '\oslash', '\ast', '\sqcup', '\triangleright', '\odot', '\star', '\vee', '\lhd$', '\bigcirc', '\circ', '\wedge', '\rhd$', '\dagger', '\bullet', '\setminus', '\unlhd$', '\ddagger', '\cdot', '\wr', '\unrhd$', '\amalg', '+', '-',
'\leq', '\geq', '\equiv', '\models', '\prec', '\succ', '\sim', '\perp', '\preceq', '\succeq', '\simeq', '\mid', '\ll', '\gg', '\asymp', '\parallel', '\subset', '\supset', '\approx', '\bowtie', '\subseteq', '\supseteq', '\cong', '\Join$', '\sqsubset$', '\sqsupset$', '\neq', '\smile', '\sqsubseteq', '\sqsupseteq', '\doteq', '\frown', '\in', '\ni', '\propto', '\vdash', '\dashv',
'\colon', '\ldotp', '\cdotp',
'\leftarrow', '\longleftarrow', '\uparrow', '\Leftarrow', '\Longleftarrow', '\Uparrow', '\rightarrow', '\longrightarrow', '\downarrow', '\Rightarrow', '\Longrightarrow', '\Downarrow', '\leftrightarrow', '\longleftrightarrow', '\updownarrow', '\Leftrightarrow', '\Longleftrightarrow', '\Updownarrow', '\mapsto', '\longmapsto', '\nearrow', '\hookleftarrow', '\hookrightarrow', '\searrow', '\leftharpoonup', '\rightharpoonup', '\swarrow', '\leftharpoondown', '\rightharpoondown', '\nwarrow', '\rightleftharpoons', '\leadsto$',
'\ldots', '\cdots', '\vdots', '\ddots', '\aleph', '\prime', '\forall', '\infty', '\hbar', '\emptyset', '\exists', '\Box$', '\imath', '\nabla', '\neg', '\Diamond$', '\jmath', '\surd', '\flat', '\triangle', '\ell', '\top', '\natural', '\clubsuit', '\wp', '\bot', '\sharp', '\diamondsuit', '\Re', '\|', '\backslash', '\heartsuit', '\Im', '\angle', '\partial', '\spadesuit', '\mho$',
'\sum', '\bigcap', '\bigodot', '\prod', '\bigcup', '\bigotimes', '\coprod', '\bigsqcup', '\bigoplus', '\int', '\bigvee', '\biguplus', '\oint', '\bigwedge',
'\arccos', '\cos', '\csc', '\exp', '\ker', '\limsup', '\min', '\sinh', '\arcsin', '\cosh', '\deg', '\gcd', '\lg', '\ln', '\Pr', '\sup', '\arctan', '\cot', '\det', '\hom', '\lim', '\log', '\sec', '\tan', '\arg', '\coth', '\dim', '\inf', '\liminf', '\max', '\sin', '\tanh',
'\uparrow', '\Uparrow', '\downarrow', '\Downarrow', '\updownarrow', '\Updownarrow', '\lfloor', '\rfloor', '\lceil', '\rceil', '\langle', '\rangle', '\backslash',
'\rmoustache', '\lmoustache', '\rgroup', '\lgroup', '\arrowvert', '\Arrowvert', '\bracevert',
'\hat{', '\acute{', '\bar{', '\dot{', '\breve{', '\check{', '\grave{', '\vec{', '\ddot{', '\tilde{',
'\widetilde{', '\widehat{', '\overleftarrow{', '\overrightarrow{', '\overline{', '\underline{', '\overbrace{', '\underbrace{', '\sqrt{', '\sqrt[', '\frac{'
);
# list of elements that can contain style elements:
my @containing_styles = ('library','problem', at responses,'foil','item','text','hintgroup','hintpart','label','part','preduedate','postanswerdate','solved','notsolved','block','while','web','standalone','problemtype','languageblock','translated','lang','window','windowlink','togglebox','instructorcomment','body','section','div','p','li','dd','td','th','blockquote','object','applet','video','audio','canvas','fieldset','button',
'span','strong','em','b','i','sup','sub','code','kbd','samp','tt','ins','del','var','small','big','u','font');
my @html_styles = ('span', 'strong', 'em' , 'b', 'i', 'sup', 'sub', 'tt', 'var', 'small', 'big', 'u');
# Parses the XML document and fixes many things to turn it into a LON-CAPA 3 document
# Returns the text of the document.
sub post_xml {
my ($textref, $new_path) = @_;
my $dom_doc = XML::LibXML->load_xml(string => $textref);
my $root = fix_structure($dom_doc);
remove_elements($root, ['startouttext','startoutext','startottext','startouttex','startouttect','atartouttext','starouttext','starttextout','starttext','starttextarea','endouttext','endoutext','endoutttext','endouttxt','endouutext','ednouttext','endouttex','endoouttext','endouttest','endtextout','endtextarea','startpartmarker','endpartmarker','basefont','x-claris-tagview','x-claris-window','x-sas-window']);
remove_empty_attributes($root);
fix_attribute_case($root);
my $fix_by_hand = replace_m($root);
my @all_block = (@block_elements, @block_html);
add_sty_blocks($new_path, $root, \@all_block); # must come before the subs using @all_block
fix_block_styles($root, \@all_block);
$root->normalize();
fix_fonts($root, \@all_block);
replace_u($root);
remove_bad_cdata_sections($root);
add_cdata_sections($root);
fix_style_element($root);
fix_tables($root);
fix_lists($root);
fix_wrong_name_for_img($root); # should be before replace_deprecated_attributes_by_css
replace_deprecated_attributes_by_css($root);
replace_center($root, \@all_block); # must come after replace_deprecated_attributes_by_css
replace_nobr($root);
remove_useless_notsolved($root);
fix_paragraphs_inside($root, \@all_block);
remove_empty_style($root);
fix_empty_lc_elements($root);
lowercase_attribute_values($root);
replace_numericalresponse_unit_attribute($root);
replace_functions_by_elements($root);
pretty($root, \@all_block);
replace_tm_dtm($root);
open my $out, '>', $new_path;
print $out $dom_doc->toString(); # byte string !
close $out;
if ($fix_by_hand) {
die "The file has been converted but it should be fixed by hand.";
}
}
sub fix_structure {
my ($doc) = @_;
# the 'loncapa' root element has already been added in pre_xml
my $root = $doc->documentElement;
# inside the root, replace html, problem and library elements by their content
my @toreplace = ('html','problem','library');
foreach my $name (@toreplace) {
my @elements = $root->getElementsByTagName($name);
foreach my $element (@elements) {
replace_by_children($element);
}
}
# insert all link and style elements inside a new head element
my $current_node = undef;
my @heads = $doc->getElementsByTagName('head');
my @links = $doc->getElementsByTagName('link');
my @styles = $doc->getElementsByTagName('style');
my @titles = $doc->getElementsByTagName('title');
if (scalar(@titles) > 0) {
# NOTE: there is a title element in gnuplot, not to be confused with the one inside HTML head
for (my $i=0; $i<scalar(@titles); $i++) {
my $title = $titles[$i];
my $found_gnuplot = 0;
my $ancestor = $title->parentNode;
while (defined $ancestor) {
if ($ancestor->nodeName eq 'gnuplot') {
$found_gnuplot = 1;
last;
}
$ancestor = $ancestor->parentNode;
}
if ($found_gnuplot) {
splice(@titles, $i, 1);
$i--;
}
}
}
if (scalar(@heads) > 0 || scalar(@titles) > 0 || scalar(@links) > 0 || scalar(@styles) > 0) {
my $htmlhead = $doc->createElement('head');
foreach my $head (@heads) {
my $next;
for (my $child=$head->firstChild; defined $child; $child=$next) {
$next = $child->nextSibling;
$head->removeChild($child);
if ($child->nodeType != XML_ELEMENT_NODE ||
string_in_array(['title','script','style','meta','link','import','base'], $child->nodeName)) {
$htmlhead->appendChild($child);
} else {
# this should not be in head
insert_after_or_first($root, $child, $current_node);
}
}
$head->parentNode->removeChild($head);
}
foreach my $child (@titles, @links, @styles) {
$child->parentNode->removeChild($child);
$htmlhead->appendChild($child);
}
insert_after_or_first($root, $htmlhead, $current_node);
$current_node = $htmlhead;
}
# body
my $htmlbody = undef;
my @bodies = $doc->getElementsByTagName('body');
if (scalar(@bodies) > 0) {
# TODO: fix content and position of body elements
if ($root->nodeName eq 'problem') {
foreach my $body (@bodies) {
replace_by_children($body);
}
}
}
# add all the meta elements afterwards when they are LON-CAPA meta. Remove all HTML meta.
my @meta_names = ('abstract','author','authorspace','avetries','avetries_list','clear','comefrom','comefrom_list','copyright','correct','count','course','course_list','courserestricted','creationdate','dependencies','depth','difficulty','difficulty_list','disc','disc_list','domain','end','field','firstname','generation','goto','goto_list','groupname','helpful','highestgradelevel','hostname','id','keynum','keywords','language','lastname','lastrevisiondate','lowestgradelevel','middlename','mime','modifyinguser','notes','owner','permanentemail','scope','sequsage','sequsage_list','standards','start','stdno','stdno_list','subject','technical','title','url','username','value','version');
my @metas = $doc->getElementsByTagName('meta');
foreach my $meta (@metas) {
$meta->parentNode->removeChild($meta);
my $name = $meta->getAttribute('name');
my $content = $meta->getAttribute('content');
if (defined $name && defined $content && string_in_array(\@meta_names, lc($name))) {
my $lcmeta = $doc->createElement('meta');
$lcmeta->setAttribute('name', lc($name));
$lcmeta->setAttribute('content', $content);
insert_after_or_first($root, $lcmeta, $current_node);
$current_node = $lcmeta;
}
}
return($root);
}
# insert the new child under parent after the reference child, or as the first child if the reference child is not defined
sub insert_after_or_first {
my ($parent, $newchild, $refchild) = @_;
if (defined $refchild) {
$parent->insertAfter($newchild, $refchild);
} elsif (defined $parent->firstChild) {
$parent->insertBefore($newchild, $parent->firstChild);
} else {
$parent->appendChild($newchild);
}
}
# removes all elements with given names inside the node, but keep the content
sub remove_elements {
my ($node, $to_remove) = @_;
my $nextChild;
for (my $child=$node->firstChild; defined $child; $child=$nextChild) {
$nextChild = $child->nextSibling;
my $type = $node->nodeType;
if ($type == XML_ELEMENT_NODE) {
if (string_in_array($to_remove, $child->nodeName)) {
my $first_non_white = $child->firstChild;
if (defined $first_non_white && $first_non_white->nodeType == XML_TEXT_NODE &&
$first_non_white->nodeValue =~ /^\s*$/) {
$first_non_white = $first_non_white->nextSibling;
}
if (defined $first_non_white) {
$nextChild = $first_non_white;
replace_by_children($child);
} else {
$node->removeChild($child);
}
} else {
remove_elements($child, $to_remove);
}
}
}
}
# removes some attributes that have an invalid empty value
sub remove_empty_attributes {
my ($root) = @_;
my $doc = $root->ownerDocument;
# this list is based on validation errors in the MSU subset (it could be more complete if it was based on the schema)
my @attributes = (
['curve', ['pointsize']],
['foil', ['location']],
['foilgroup', ['checkboxoptions', 'options', 'texoptions']],
['gnuplot', ['pattern', 'texwidth']],
['img', ['height', 'texheight', 'texwidth', 'texwrap', 'width']],
['import', ['importmode']],
['optionresponse', ['max']],
['organicstructure', ['options']],
['radiobuttonresponse', ['max']],
['randomlabel', ['height', 'texwidth', 'width']],
['stringresponse', ['type']],
['textline', ['size']],
);
foreach my $element_attributes (@attributes) {
my $element_name = $element_attributes->[0];
my $attribute_names = $element_attributes->[1];
my @elements = $doc->getElementsByTagName($element_name);
foreach my $element (@elements) {
foreach my $attribute_name (@$attribute_names) {
my $value = $element->getAttribute($attribute_name);
if (defined $value && $value =~ /^\s*$/) {
$element->removeAttribute($attribute_name);
}
}
}
}
}
# fixes the case for a few attributes that are not all lowercase
# (the HTML parser used in html_to_xml turns everything lowercase, which is a good thing in general)
sub fix_attribute_case {
my ($root) = @_;
my $doc = $root->ownerDocument;
my @attributes = (
['labelgroup', ['TeXsize']],
['h1', ['TeXsize']],
['h2', ['TeXsize']],
['h3', ['TeXsize']],
['h4', ['TeXsize']],
['h5', ['TeXsize']],
['h6', ['TeXsize']],
# font and basefont have a TeXsize but will be removed
['optionresponse', ['TeXlayout']],
['itemgroup', ['TeXitemgroupwidth']],
['Task', ['OptionalRequired']],
['Question', ['OptionalRequired','Mandatory']],
['Instance', ['OptionalRequired','Disabled']],
['Criteria', ['Mandatory']],
['table', ['TeXwidth','TeXtheme']],
['td', ['TeXwidth']],
['th', ['TeXwidth']],
['img', ['TeXwidth','TeXheight','TeXwrap']],
);
foreach my $element_attributes (@attributes) {
my $element_name = $element_attributes->[0];
my $attribute_names = $element_attributes->[1];
my @elements = $doc->getElementsByTagName($element_name);
foreach my $element (@elements) {
foreach my $attribute_name (@$attribute_names) {
my $value = $element->getAttribute(lc($attribute_name));
if (defined $value) {
$element->removeAttribute(lc($attribute_name));
$element->setAttribute($attribute_name, $value);
}
}
}
}
}
# Replaces m by HTML, tm and/or dtm (which will be replaced by <m> later, but they are useful
# to know if the element is a block element or not).
# m might contain non-math LaTeX, while tm and dtm may only contain math.
# Returns 1 if the file should be fixed by hand, 0 otherwise.
sub replace_m {
my ($root) = @_;
my $doc = $root->ownerDocument;
my $fix_by_hand = 0;
# search for variable declarations
my @variables = ();
my @scripts = $root->getElementsByTagName('script');
foreach my $script (@scripts) {
my $type = $script->getAttribute('type');
if (defined $type && $type eq 'loncapa/perl') {
if (defined $script->firstChild && $script->firstChild->nodeType == XML_TEXT_NODE) {
my $text = $script->firstChild->nodeValue;
# NOTE: we are not interested in replacing "@value", only "$value"
# this regexp is for " $a = ..." and " $a[...] = ..."
while ($text =~ /^[ \t]*\$([a-zA-Z_0-9]+)(?:\[[^\]]+\])?[ \t]*=/gm) {
if (!string_in_array(\@variables, $1)) {
push(@variables, $1);
}
}
# this regexp is for "...; $a = ..." and "...; $a[...] = ..."
while ($text =~ /^[^'"\/;]+;[ \t]*\$([a-zA-Z_0-9]+)(?:\[[^\]]+\])?[ \t]*=/gm) {
if (!string_in_array(\@variables, $1)) {
push(@variables, $1);
}
}
# this regexp is for " ($a, $b, $c) = ..."
my @matches = ($text =~ /^[ \t]*\([ \t]*\$([a-zA-Z_0-9]+)(?:[ \t]*,[ \t]*\$([a-zA-Z_0-9]+))*[ \t]*\)[ \t]*=/gm);
foreach my $match (@matches) {
if (!defined $match) {
next; # not sure why it happens, but it does
}
if (!string_in_array(\@variables, $match)) {
push(@variables, $match);
}
}
# and this one is for "push @a"
while ($text =~ /^[ \t]*push @([a-zA-Z_0-9]+)[ \t,]*/gm) {
if (!string_in_array(\@variables, $1)) {
push(@variables, $1);
}
}
# use the opportunity to report usage of <m> in Perl scripts
if ($text =~ /^[^#].*<m[ >]/m) {
print "WARNING: <m> is used in a script, it should be converted by hand\n";
$fix_by_hand = 1;
}
}
}
}
my @ms = $root->getElementsByTagName('m');
foreach my $m (@ms) {
if (!defined $m->firstChild) {
$m->parentNode->removeChild($m);
next;
}
if (defined $m->firstChild->nextSibling || $m->firstChild->nodeType != XML_TEXT_NODE) {
print "WARNING: m value is not simple text\n";
$fix_by_hand = 1;
next;
}
my $text = $m->firstChild->nodeValue;
my $text_before_variable_replacement = $text;
my $var_key1 = 'dfhg3df54hg65hg4';
my $var_key2 = 'dfhg654d6f5g4h5f';
my $eval = defined $m->getAttribute('eval') && $m->getAttribute('eval') eq 'on';
if ($eval) {
# replace variables
foreach my $variable (@variables) {
my $replacement = $var_key1.$variable.$var_key2;
$text =~ s/\$$variable(?![a-zA-Z])/$replacement/ge;
$text =~ s/\$\{$variable\}/$replacement/ge;
}
}
# check if the expression is enclosed in math separators: $ $$ \( \) \[ \]
# if so, replace the whole node by dtm or tm
my $new_text;
my $new_node_name;
if ($text =~ /^\s*\$\$([^\$]*)\$\$\s*$/) {
$new_node_name = 'dtm';
$new_text = $1;
} elsif ($text =~ /^\s*\\\[(.*)\\\]\s*$/s) {
$new_node_name = 'dtm';
$new_text = $1;
} elsif ($text =~ /^\s*\$([^\$]*)\$\s*$/) {
$new_node_name = 'tm';
$new_text = $1;
} elsif ($text =~ /^\s*\\\((.*)\\\)\s*$/s) {
$new_node_name = 'tm';
$new_text = $1;
}
if (defined $new_node_name) {
if ($eval) {
foreach my $variable (@variables) {
my $replacement = $var_key1.$variable.$var_key2;
$new_text =~ s/$replacement([a-zA-Z])/\${$variable}$1/g;
$new_text =~ s/$replacement/\$$variable/g;
}
}
my $new_node = $doc->createElement($new_node_name);
if ($eval) {
$new_node->setAttribute('eval', 'on');
}
$new_node->appendChild($doc->createTextNode($new_text));
$m->parentNode->replaceChild($new_node, $m);
next;
}
if ($text !~ /\$|\\\(|\\\)|\\\[|\\\]/) {
# there are no math separators inside
# try to guess if this is meant as math
my $found_math = 0;
foreach my $symbol (@latex_math) {
if (index($text, $symbol) != -1) {
$found_math = 1;
last;
}
}
if ($found_math) {
# interpret the whole text as LaTeX inline math
my $new_node = $doc->createElement('tm');
if ($eval) {
$new_node->setAttribute('eval', 'on');
}
$new_node->appendChild($doc->createTextNode($text_before_variable_replacement));
$m->parentNode->replaceChild($new_node, $m);
next;
}
# no math symbol found, we will convert the text with tth
}
# there are math separators inside, even after hiding variables, or there was no math symbol
# hide math parts inside before running tth
my $math_key1 = '#ghjgdh5hg45gf';
my $math_key2 = '#';
my @maths = ();
my @separators = (['$$','$$'], ['\\(','\\)'], ['\\[','\\]'], ['$','$']);
foreach my $seps (@separators) {
my $sep1 = $seps->[0];
my $sep2 = $seps->[1];
my $pos1 = index($text, $sep1);
if ($pos1 == -1) {
next;
}
my $pos2 = index($text, $sep2, $pos1+length($sep1));
while ($pos1 != -1 && $pos2 != -1) {
my $replace = substr($text, $pos1, $pos2+length($sep2)-$pos1);
push(@maths, $replace);
my $by = $math_key1.scalar(@maths).$math_key2;
$text = substr($text, 0, $pos1).$by.substr($text, $pos2+length($sep2));
$pos1 = index($text, $sep1);
if ($pos1 != -1) {
$pos2 = index($text, $sep2, $pos1+length($sep1));
}
}
}
# get HTML as text from tth
my $html_text = tth($text);
# replace math by replacements
for (my $i=0; $i < scalar(@maths); $i++) {
my $math = $maths[$i];
$math =~ s/&/&/g;
$math =~ s/</</g;
$math =~ s/>/>/g;
if ($math =~ /^\$\$(.*)\$\$$/s) {
$math = '<dtm>'.$1.'</dtm>';
} elsif ($math =~ /^\\\[(.*)\\\]$/s) {
$math = '<dtm>'.$1.'</dtm>';
} elsif ($math =~ /^\\\((.*)\\\)$/s) {
$math = '<tm>'.$1.'</tm>';
} elsif ($math =~ /^\$(.*)\$$/s) {
$math = '<tm>'.$1.'</tm>';
}
my $replace = $math_key1.($i+1).$math_key2;
$html_text =~ s/$replace/$math/;
}
# replace variables if necessary
if ($eval) {
foreach my $variable (@variables) {
my $replacement = $var_key1.$variable.$var_key2;
$html_text =~ s/$replacement([a-zA-Z])/\${$variable}$1/g;
$html_text =~ s/$replacement/\$$variable/g;
}
}
my $fragment = html_to_dom($html_text);
$doc->adoptNode($fragment);
$m->parentNode->replaceChild($fragment, $m);
}
return $fix_by_hand;
}
# Returns the HTML equivalent of LaTeX input, using tth
sub tth {
my ($text) = @_;
my ($fh, $tmp_path) = tempfile();
binmode($fh, ':utf8');
print $fh $text;
close $fh;
my $output = `tth -r -w2 -u -y0 < $tmp_path 2>/dev/null`;
# hopefully the temp file will not be removed before this point (otherwise we should use unlink_on_destroy 0)
$output =~ s/^\s*|\s*$//;
$output =~ s/<div class="p"><!----><\/div>/<br\/>/; # why is tth using such ugly markup for \newline ?
return $output;
}
# transform simple HTML into a DOM fragment (which will need to be adopted by the document)
sub html_to_dom {
my ($text) = @_;
$text = '<root>'.$text.'</root>';
my $textref = html_to_xml::html_to_xml(\$text);
utf8::upgrade($$textref); # otherwise the XML parser fails when the HTML parser turns into a character
my $dom_doc = XML::LibXML->load_xml(string => $textref);
my $root = $dom_doc->documentElement;
remove_empty_style($root);
my $fragment = $dom_doc->createDocumentFragment();
my $next;
for (my $n=$root->firstChild; defined $n; $n=$next) {
$next = $n->nextSibling;
$root->removeChild($n);
$fragment->appendChild($n);
}
return($fragment);
}
# Use the linked sty files to guess which newly defined elements should be considered blocks.
# Also adds to @containing_styles the sty elements that contain styles.
# @param {string} fn - the .lc file path (we only extract the directory path from it)
sub add_sty_blocks {
my ($fn, $root, $all_block) = @_;
my $doc = $root->ownerDocument;
my @parserlibs = $doc->getElementsByTagName('parserlib');
my @libs = ();
foreach my $parserlib (@parserlibs) {
if (defined $parserlib->firstChild && $parserlib->firstChild->nodeType == XML_TEXT_NODE) {
my $value = $parserlib->firstChild->nodeValue;
$value =~ s/^\s+|\s+$//g;
if ($value ne '') {
push(@libs, $value);
}
}
}
my ($name, $path, $suffix) = fileparse($fn);
foreach my $sty (@libs) {
if (substr($sty, 0, 1) eq '/') {
$sty = $RES_DIR.$sty;
} else {
$sty = $path.$sty;
}
my $new_elements = parse_sty($sty, $all_block);
better_guess($root, $new_elements, $all_block);
my $new_blocks = $new_elements->{'block'};
my $new_inlines = $new_elements->{'inline'};
push(@$all_block, @{$new_blocks});
#push(@inlines, @{$new_inlines}); # we are not using a list of inline elements at this point
}
}
##
# Parses a sty file and returns lists of block and inline elements.
# @param {string} fn - the file path
##
sub parse_sty {
my ($fn, $all_block) = @_;
my @blocks = ();
my @inlines = ();
my $p = HTML::TokeParser->new($fn);
if (! $p) {
die "post_xml.pl: parse_sty: Error reading $fn\n";
}
$p->empty_element_tags(1);
my $in_definetag = 0;
my $in_render = 0;
my %newtags = ();
my $newtag = '';
my $is_block = 0;
while (my $token = $p->get_token) {
if ($token->[0] eq 'S') {
my $tag = lc($token->[1]);
if ($tag eq 'definetag') {
$in_definetag = 1;
$is_block = 0;
my $attributes = $token->[2];
$newtag = $attributes->{'name'};
if (substr($newtag, 0, 1) eq '/') {
$newtag = substr($newtag, 1);
}
} elsif ($in_definetag && $tag eq 'render') {
$in_render = 1;
$is_block = 0;
} elsif ($in_render) {
if (string_in_array($all_block, $tag)) {
$is_block = 1;
}
}
} elsif ($token->[0] eq 'E') {
my $tag = lc($token->[1]);
if ($tag eq 'definetag') {
$in_definetag = 0;
if (defined $newtags{$newtag}) {
$newtags{$newtag} = $newtags{$newtag} || $is_block;
} else {
$newtags{$newtag} = $is_block;
}
} elsif ($in_definetag && $tag eq 'render') {
$in_render = 0;
}
}
}
foreach $newtag (keys(%newtags)) {
if ($newtags{$newtag} == 1) {
push(@blocks, $newtag);
} else {
push(@inlines, $newtag);
}
}
return {'block'=>\@blocks, 'inline'=>\@inlines};
}
##
# Marks as block the elements that contain block elements in the input file.
# Also adds to @containing_styles the sty elements that contain styles.
# @param {string} fn - the file path
# @param {Hash<string,Array>} new_elements - contains arrays in 'block' and 'inline'
##
sub better_guess {
my ($root, $new_elements, $all_block) = @_;
my $new_blocks = $new_elements->{'block'};
my $new_inlines = $new_elements->{'inline'};
my @change = (); # change these elements from inline to block
foreach my $tag (@{$new_inlines}) {
my @nodes = $root->getElementsByTagName($tag);
NODE_LOOP: foreach my $node (@nodes) {
for (my $child=$node->firstChild; defined $child; $child=$child->nextSibling) {
if ($child->nodeType == XML_ELEMENT_NODE) {
if (string_in_array($all_block, $child->nodeName) || string_in_array($new_blocks, $child->nodeName)) {
push(@change, $tag);
last NODE_LOOP;
}
}
}
}
}
foreach my $inline (@change) {
my $index = 0;
$index++ until $new_inlines->[$index] eq $inline;
splice(@{$new_inlines}, $index, 1);
push(@{$new_blocks}, $inline);
}
# add to @containing_styles when a style is used inside
# NOTE: some sty elements will be added even though they should not, but if we don't do that
# all style will be removed in the sty elements.
foreach my $tag ((@{$new_blocks}, @{$new_inlines})) {
my @nodes = $root->getElementsByTagName($tag);
NODE_LOOP: foreach my $node (@nodes) {
for (my $child=$node->firstChild; defined $child; $child=$child->nextSibling) {
if ($child->nodeType == XML_ELEMENT_NODE) {
if (string_in_array(\@html_styles, $child->nodeName)) {
push(@containing_styles, $tag);
last NODE_LOOP;
}
}
}
}
}
}
# When a style element contains a block, move the style inside the block where it is allowed.
# style/block/other -> block/style/other
# When a style is used where it is not allowed, move it inside its children or remove it (unless it contains only text)
# element_not_containing_styles/style/other -> element_not_containing_styles/other/style (except if other is a style)
# The fix is not perfect in the case of element_not_containing_styles/style1/style2/block/text (style1 will be lost):
# element_not_containing_styles/style1/style2/block/text -> element_not_containing_styles/block/style2/text
# (a solution to this problem would be to merge the styles in a span)
# NOTE: .sty defined elements might have been added to @containing_styles by better_guess().
sub fix_block_styles {
my ($element, $all_block) = @_;
my $doc = $element->ownerDocument;
if (string_in_array(\@html_styles, $element->nodeName)) {
# move spaces out of the style element
if (defined $element->firstChild && $element->firstChild->nodeType == XML_TEXT_NODE) {
my $child = $element->firstChild;
if ($child->nodeValue =~ /^(\s+)(\S.*)$/s) {
$element->parentNode->insertBefore($doc->createTextNode($1), $element);
$child->setData($2);
}
}
if (defined $element->lastChild && $element->lastChild->nodeType == XML_TEXT_NODE) {
my $child = $element->lastChild;
if ($child->nodeValue =~ /^(.*\S)(\s+)$/s) {
$element->parentNode->insertAfter($doc->createTextNode($2), $element);
$child->setData($1);
}
}
my $found_block = 0;
for (my $child=$element->firstChild; defined $child; $child=$child->nextSibling) {
if ($child->nodeType == XML_ELEMENT_NODE && string_in_array($all_block, $child->nodeName)) {
$found_block = 1;
last;
}
}
my $no_style_here = !string_in_array(\@containing_styles, $element->parentNode->nodeName);
if ($found_block || $no_style_here) {
# there is a block or the style is not allowed here,
# the style element has to be replaced by its modified children
my $s; # a clone of the style
my $next;
for (my $child=$element->firstChild; defined $child; $child=$next) {
$next = $child->nextSibling;
if ($child->nodeType == XML_ELEMENT_NODE && (string_in_array($all_block, $child->nodeName) ||
$child->nodeName eq 'br' || $no_style_here)) {
# avoid inverting a style with a style with $no_style_here (that would cause endless recursion)
if (!$no_style_here || (!string_in_array(\@html_styles, $child->nodeName) &&
string_in_array(\@containing_styles, $child->nodeName))) {
# block node or inline node when the style is not allowed:
# move all children inside the style, and make the style the only child
$s = $element->cloneNode();
my $next2;
for (my $child2=$child->firstChild; defined $child2; $child2=$next2) {
$next2 = $child2->nextSibling;
$child->removeChild($child2);
$s->appendChild($child2);
}
$child->appendChild($s);
}
$s = undef;
} elsif (($child->nodeType == XML_TEXT_NODE && $child->nodeValue !~ /^\s*$/) ||
$child->nodeType == XML_ELEMENT_NODE) {
# if the style is allowed, move text and inline nodes inside the style
if (!$no_style_here) {
if (!defined $s) {
$s = $element->cloneNode();
$element->insertBefore($s, $child);
}
$element->removeChild($child);
$s->appendChild($child);
}
} else {
# do not put other nodes inside the style
$s = undef;
}
}
# now replace by children and fix them
my $parent = $element->parentNode;
for (my $child=$element->firstChild; defined $child; $child=$next) {
$next = $child->nextSibling;
$element->removeChild($child);
$parent->insertBefore($child, $element);
if ($child->nodeType == XML_ELEMENT_NODE) {
fix_block_styles($child, $all_block);
}
}
$parent->removeChild($element);
return;
}
}
# otherwise fix all children
my $next;
for (my $child=$element->firstChild; defined $child; $child=$next) {
$next = $child->nextSibling;
if ($child->nodeType == XML_ELEMENT_NODE) {
fix_block_styles($child, $all_block);
}
}
}
# removes empty font elements and font elements that contain at least one block element
# replaces other font elements by equivalent span
sub fix_fonts {
my ($root, $all_block) = @_;
my $doc = $root->ownerDocument;
my @fonts = $root->getElementsByTagName('font');
@fonts = reverse(@fonts); # to deal with the ancestor last in the case of font/font
foreach my $font (@fonts) {
my $block = 0;
for (my $child=$font->firstChild; defined $child; $child=$child->nextSibling) {
if (string_in_array($all_block, $child->nodeName) || string_in_array(\@inline_like_block, $child->nodeName)) {
$block = 1;
last;
}
}
if (!defined $font->firstChild || $block) {
# empty font or font containing block elements
# replace this node by its content
replace_by_children($font);
} else {
# replace by equivalent span
my $color = get_non_empty_attribute($font, 'color');
my $size = get_non_empty_attribute($font, 'size');
my $face = get_non_empty_attribute($font, 'face');
if (defined $face) {
$face =~ s/^,|,$//;
}
if (!defined $color && !defined $size && !defined $face) {
# useless font element: replace this node by its content
replace_by_children($font);
next;
}
my $replacement;
tie (my %properties, 'Tie::IxHash', ());
if (!defined $color && !defined $size && defined $face && lc($face) eq 'symbol') {
$replacement = $doc->createDocumentFragment();
} else {
$replacement = $doc->createElement('span');
my $css = '';
if (defined $color) {
$color =~ s/^x//;
$properties{'color'} = $color;
}
if (defined $size) {
my %hash = (
'1' => 'x-small',
'2' => 'small',
'3' => 'medium',
'4' => 'large',
'5' => 'x-large',
'6' => 'xx-large',
'7' => '300%',
'-1' => 'small',
'-2' => 'x-small',
'+1' => 'large',
'+2' => 'x-large',
'+3' => 'xx-large',
'+4' => '300%',
);
my $value = $hash{$size};
if (!defined $value) {
$value = 'medium';
}
$properties{'font-size'} = $value;
}
if (defined $face) {
if (lc($face) ne 'symbol' && lc($face) ne 'bold') {
$properties{'font-family'} = $face;
}
}
set_css_properties($replacement, \%properties);
}
if (defined $face && lc($face) eq 'symbol') {
# convert all content to unicode
my $next;
for (my $child=$font->firstChild; defined $child; $child=$next) {
$next = $child->nextSibling;
if ($child->nodeType == XML_TEXT_NODE) {
my $value = $child->nodeValue;
$value =~ tr/ABGDEZHQIKLMNXOPRSTUFCYWabgdezhqiklmnxoprVstufcywJjv¡«¬®/ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎΠΡΣΤΥΦΧΨΩαβγδεζηθικλμνξοÏÏÏÏÏÏ
ÏÏÏÏÏÏÏÏâââ/;
$child->setData($value);
}
}
}
# replace the font node
if ($replacement->nodeType == XML_ELEMENT_NODE && !defined $font->previousSibling &&
!defined $font->nextSibling && string_in_array(\@accepting_style, $font->parentNode->nodeName)) {
# use CSS on the parent block and replace font by its children instead of using a new element
set_css_properties($font->parentNode, \%properties);
replace_by_children($font);
} else {
# move all font children inside the replacement (span or fragment)
my $next;
for (my $child=$font->firstChild; defined $child; $child=$next) {
$next = $child->nextSibling;
$font->removeChild($child);
$replacement->appendChild($child);
}
# replace font
$font->parentNode->replaceChild($replacement, $font);
}
}
}
$root->normalize();
}
# replaces u by <span style="text-decoration: underline">
sub replace_u {
my ($root) = @_;
my $doc = $root->ownerDocument;
my @us = $root->getElementsByTagName('u');
foreach my $u (@us) {
my $span = $doc->createElement('span');
$span->setAttribute('style', 'text-decoration: underline');
my $next;
for (my $child=$u->firstChild; defined $child; $child=$next) {
$next = $child->nextSibling;
$u->removeChild($child);
$span->appendChild($child);
}
$u->parentNode->replaceChild($span, $u);
}
}
# removes CDATA sections tags that have not been parsed correcty by the HTML parser
# also removes bad comments in script elements
sub remove_bad_cdata_sections {
my ($root) = @_;
my $doc = $root->ownerDocument;
foreach my $name (@preserve_elements) {
my @nodes = $root->getElementsByTagName($name);
foreach my $node (@nodes) {
if (defined $node->firstChild && $node->firstChild->nodeType == XML_TEXT_NODE) {
my $value = $node->firstChild->nodeValue;
if ($name eq 'script' && (!defined $node->getAttribute('type') || $node->getAttribute('type') ne 'loncapa/perl') &&
!defined $node->firstChild->nextSibling && $value =~ /^(\s*)<!--(.*)-->(\s*)$/) {
# web browsers interpret that as a real comment when it is on 1 line, but the Perl HTML parser thinks it is the script
# -> turning it back into a comment
# (this is only true for Javascript script elements, since LON-CAPA does not parse loncapa/perl scripts in the same way)
$node->removeChild($node->firstChild);
$node->appendChild($doc->createComment($2));
next;
}
# at the beginning:
$value =~ s/^(\s*)<!\[CDATA\[/$1/; # <![CDATA[
$value =~ s/^(\s*)\/\*\s*<!\[CDATA\[\s*\*\//$1/; # /* <![CDATA[ */
$value =~ s/^(\s*)\/\/\s*<!\[CDATA\[/$1/; # // <![CDATA[
$value =~ s/^(\s*)(\/\/)?\s*<!--/$1/; # // <!--
# at the end:
$value =~ s/\/\/\s*\]\]>(\s*)$/$1/; # // ]]>
$value =~ s/\]\]>(\s*)$/$1/; # ]]>
$value =~ s/(\/\/)?\s*-->(\s*)$/$2/; # // -->
$value =~ s/\/\*\s*\]\]>\s*\*\/(\s*)$/$1/; # /* ]]> */
$value = "\n".$value."\n";
$value =~ s/\s*(\n[ \t]*)/$1/;
$value =~ s/\s+$/\n/;
$node->firstChild->setData($value);
}
}
}
}
# adds CDATA sections to scripts
sub add_cdata_sections {
my ($root) = @_;
my $doc = $root->ownerDocument;
my @scripts = $root->getElementsByTagName('script');
my @answers = $root->getElementsByTagName('answer');
foreach my $answer (@answers) {
my $ancestor = $answer->parentNode;
my $found_capa_response = 0;
while (defined $ancestor) {
if ($ancestor->nodeName eq 'numericalresponse' || $ancestor->nodeName eq 'formularesponse') {
$found_capa_response = 1;
last;
}
$ancestor = $ancestor->parentNode;
}
if (!$found_capa_response) {
push(@scripts, $answer);
}
}
foreach my $script (@scripts) {
# use a CDATA section in the normal situation, for any script
my $first = $script->firstChild;
if (defined $first && $first->nodeType == XML_TEXT_NODE && !defined $first->nextSibling) {
my $cdata = $doc->createCDATASection($first->nodeValue);
$script->replaceChild($cdata, $first);
}
}
}
# removes "<!--" and "-->" at the beginning and end of style elements
sub fix_style_element {
my ($root) = @_;
my @styles = $root->getElementsByTagName('style');
foreach my $style (@styles) {
if (defined $style->firstChild && $style->firstChild->nodeType == XML_TEXT_NODE &&
!defined $style->firstChild->nextSibling) {
my $text = $style->firstChild->nodeValue;
if ($text =~ /^\s*<!--(.*)-->\s*$/s) {
$style->firstChild->setData($1);
}
}
}
}
# create missing cells at the end of table rows
sub fix_tables {
my ($root) = @_;
my @tables = $root->getElementsByTagName('table');
foreach my $table (@tables) {
fix_cells($table);
foreach my $tbody ($table->getChildrenByTagName('tbody')) {
fix_cells($tbody);
}
foreach my $thead ($table->getChildrenByTagName('thead')) {
fix_cells($thead);
}
foreach my $tfoot ($table->getChildrenByTagName('tfoot')) {
fix_cells($tfoot);
}
}
}
# create missing cells at the end of table rows
sub fix_cells {
my ($table) = @_; # could actually be table, tbody, thead or tfoot
my $doc = $table->ownerDocument;
my @nb_cells = ();
my $max_nb_cells = 0;
my @rowspans = ();
my @trs = $table->getChildrenByTagName('tr');
foreach my $tr (@trs) {
my $nb_cells;
if (defined $rowspans[0]) {
$nb_cells = shift(@rowspans);
} else {
$nb_cells = 0;
}
for (my $cell=$tr->firstChild; defined $cell; $cell=$cell->nextSibling) {
if ($cell->nodeName eq 'td' || $cell->nodeName eq 'th') {
my $colspan = $cell->getAttribute('colspan');
if (defined $colspan && $colspan =~ /^\s*[0-9]+\s*$/) {
$nb_cells += $colspan;
} else {
$nb_cells++;
}
my $rowspan = $cell->getAttribute('rowspan');
if (defined $rowspan && $rowspan =~ /^\s*[0-9]+\s*$/) {
for (my $i=0; $i < $rowspan-1; $i++) {
if (!defined $rowspans[$i]) {
$rowspans[$i] = 1;
} else {
$rowspans[$i]++;
}
}
}
}
}
push(@nb_cells, $nb_cells);
if ($nb_cells > $max_nb_cells) {
$max_nb_cells = $nb_cells;
}
}
foreach my $tr (@trs) {
my $nb_cells = shift(@nb_cells);
if ($nb_cells < $max_nb_cells) {
for (1..($max_nb_cells - $nb_cells)) {
$tr->appendChild($doc->createElement('td'));
}
}
}
}
# replaces ul/ul by ul/li/ul and the same for ol (using the previous li if possible)
# also adds a ul element when a li has no ul/ol ancestor
sub fix_lists {
my ($root) = @_;
my $doc = $root->ownerDocument;
my @uls = $root->getElementsByTagName('ul');
my @ols = $root->getElementsByTagName('ol');
my @lists = (@uls, @ols);
foreach my $list (@lists) {
my $next;
for (my $child=$list->firstChild; defined $child; $child=$next) {
$next = $child->nextSibling;
if ($child->nodeType == XML_ELEMENT_NODE && string_in_array(['ul','ol'], $child->nodeName)) {
my $previous = $child->previousNonBlankSibling(); # note: non-DOM method
$list->removeChild($child);
if (defined $previous && $previous->nodeType == XML_ELEMENT_NODE && $previous->nodeName eq 'li') {
$previous->appendChild($child);
} else {
my $li = $doc->createElement('li');
$li->appendChild($child);
if (!defined $next) {
$list->appendChild($li);
} else {
$list->insertBefore($li, $next);
}
}
}
}
}
my @lis = $root->getElementsByTagName('li');
foreach my $li (@lis) {
my $found_list_ancestor = 0;
my $ancestor = $li->parentNode;
while (defined $ancestor) {
if ($ancestor->nodeName eq 'ul' || $ancestor->nodeName eq 'ol') {
$found_list_ancestor = 1;
last;
}
$ancestor = $ancestor->parentNode;
}
if (!$found_list_ancestor) {
# replace li by ul and add li under ul
my $ul = $doc->createElement('ul');
$li->parentNode->insertBefore($ul, $li);
$li->parentNode->removeChild($li);
$ul->appendChild($li);
# add all other li afterwards inside ul (there might be text nodes in-between)
my $next = $ul->nextSibling;
while (defined $next) {
my $next_next = $next->nextSibling;
if ($next->nodeType == XML_TEXT_NODE && $next->nodeValue =~ /^\s*$/ &&
defined $next_next && $next_next->nodeType == XML_ELEMENT_NODE && $next_next->nodeName eq 'li') {
$next->parentNode->removeChild($next);
$ul->appendChild($next);
$next = $next_next;
$next_next = $next_next->nextSibling;
}
if ($next->nodeType == XML_ELEMENT_NODE && $next->nodeName eq 'li') {
$next->parentNode->removeChild($next);
$ul->appendChild($next);
} else {
last;
}
$next = $next_next;
}
}
}
}
# Some "image" elements are actually img element with a wrong name. This renames them.
# Amazingly enough, "<image src=..." displays an image in some browsers
# ("image" has existed at some point as an experimental HTML element).
sub fix_wrong_name_for_img {
my ($root) = @_;
my @images = $root->getElementsByTagName('image');
foreach my $image (@images) {
if (!defined $image->getAttribute('src')) {
next;
}
my $found_correct_ancestor = 0;
my $ancestor = $image->parentNode;
while (defined $ancestor) {
if ($ancestor->nodeName eq 'drawimage' || $ancestor->nodeName eq 'imageresponse') {
$found_correct_ancestor = 1;
last;
}
$ancestor = $ancestor->parentNode;
}
if ($found_correct_ancestor) {
next;
}
# this really has to be renamed "img"
$image->setNodeName('img');
}
}
# Replaces many deprecated attributes and replaces them by equivalent CSS when possible
sub replace_deprecated_attributes_by_css {
my ($root) = @_;
fix_deprecated_in_tables($root);
fix_deprecated_in_table_rows($root);
fix_deprecated_in_table_cells($root);
fix_deprecated_in_lists($root);
fix_deprecated_in_list_items($root);
fix_deprecated_in_hr($root);
fix_deprecated_in_img($root);
fix_deprecated_in_body($root);
fix_align_attribute($root);
}
# Replaces deprecated attributes in tables
sub fix_deprecated_in_tables {
my ($root) = @_;
my @tables = $root->getElementsByTagName('table');
foreach my $table (@tables) {
tie (my %new_properties, 'Tie::IxHash', ());
my $align = $table->getAttribute('align');
if (defined $align) {
$table->removeAttribute('align');
$align = lc(trim($align));
}
if ($table->parentNode->nodeName eq 'center' || (defined $align && $align eq 'center') ||
(defined $table->parentNode->getAttribute('align') && $table->parentNode->getAttribute('align') eq 'center')) {
$new_properties{'margin-left'} = 'auto';
$new_properties{'margin-right'} = 'auto';
}
if (defined $align && ($align eq 'left' || $align eq 'right')) {
$new_properties{'float'} = $align;
}
my $width = $table->getAttribute('width');
if (defined $width) {
$table->removeAttribute('width');
$width = trim($width);
if ($width =~ /^[0-9]+$/) {
$width .= 'px';
}
if ($width ne '') {
$new_properties{'width'} = $width;
}
}
my $height = $table->getAttribute('height');
if (defined $height) {
$table->removeAttribute('height');
# no replacement for table height
}
my $bgcolor = $table->getAttribute('bgcolor');
if (defined $bgcolor) {
$table->removeAttribute('bgcolor');
$bgcolor = trim($bgcolor);
$bgcolor =~ s/^x\s*//;
if ($bgcolor ne '') {
$new_properties{'background-color'} = $bgcolor;
}
}
my $frame = $table->getAttribute('frame');
if (defined $frame) {
$table->removeAttribute('frame');
$frame = lc(trim($frame));
if ($frame eq 'void') {
$new_properties{'border'} = 'none';
} elsif ($frame eq 'above') {
$new_properties{'border-top'} = '1px solid black';
} elsif ($frame eq 'below') {
$new_properties{'border-bottom'} = '1px solid black';
} elsif ($frame eq 'hsides') {
$new_properties{'border-top'} = '1px solid black';
$new_properties{'border-bottom'} = '1px solid black';
} elsif ($frame eq 'vsides') {
$new_properties{'border-left'} = '1px solid black';
$new_properties{'border-right'} = '1px solid black';
} elsif ($frame eq 'lhs') {
$new_properties{'border-left'} = '1px solid black';
} elsif ($frame eq 'rhs') {
$new_properties{'border-right'} = '1px solid black';
} elsif ($frame eq 'box') {
$new_properties{'border'} = '1px solid black';
} elsif ($frame eq 'border') {
$new_properties{'border'} = '1px solid black';
}
}
if (scalar(keys %new_properties) > 0) {
set_css_properties($table, \%new_properties);
}
# we can't replace the border attribute without creating a style block, but we can improve things like border="BORDER"
my $border = $table->getAttribute('border');
if (defined $border) {
$border = trim($border);
if ($border !~ /^\s*[0-9]+\s*(px)?\s*$/) {
$table->setAttribute('border', '1');
}
}
}
}
# Replaces deprecated attributes in tr elements
sub fix_deprecated_in_table_rows {
my ($root) = @_;
my @trs = $root->getElementsByTagName('tr');
foreach my $tr (@trs) {
my $old_properties = get_css_properties($tr);
tie (my %new_properties, 'Tie::IxHash', ());
my $bgcolor = $tr->getAttribute('bgcolor');
if (defined $bgcolor) {
$tr->removeAttribute('bgcolor');
if (!defined $old_properties->{'background-color'}) {
$bgcolor = trim($bgcolor);
$bgcolor =~ s/^x\s*//;
if ($bgcolor ne '') {
$new_properties{'background-color'} = $bgcolor;
}
}
}
my $align = $tr->getAttribute('align');
if (defined $align && $align !~ /\s*char\s*/i) {
$tr->removeAttribute('align');
if (!defined $old_properties->{'text-align'}) {
$align = lc(trim($align));
if ($align ne '') {
$new_properties{'text-align'} = $align;
}
}
}
my $valign = $tr->getAttribute('valign');
if (defined $valign) {
$tr->removeAttribute('valign');
if (!defined $old_properties->{'vertical-align'}) {
$valign = lc(trim($valign));
if ($valign ne '') {
$new_properties{'vertical-align'} = $valign;
}
}
}
if (scalar(keys %new_properties) > 0) {
set_css_properties($tr, \%new_properties);
}
}
}
# Replaces deprecated attributes in table cells (td and th)
sub fix_deprecated_in_table_cells {
my ($root) = @_;
my @tds = $root->getElementsByTagName('td');
my @ths = $root->getElementsByTagName('th');
my @cells = (@tds, @ths);
foreach my $cell (@cells) {
my $old_properties = get_css_properties($cell);
tie (my %new_properties, 'Tie::IxHash', ());
my $width = $cell->getAttribute('width');
if (defined $width) {
$cell->removeAttribute('width');
if (!defined $old_properties->{'width'}) {
$width = trim($width);
if ($width =~ /^[0-9]+$/) {
$width .= 'px';
}
if ($width ne '') {
$new_properties{'width'} = $width;
}
}
}
my $height = $cell->getAttribute('height');
if (defined $height) {
$cell->removeAttribute('height');
if (!defined $old_properties->{'height'}) {
$height = trim($height);
if ($height =~ /^[0-9]+$/) {
$height .= 'px';
}
if ($height ne '') {
$new_properties{'height'} = $height;
}
}
}
my $bgcolor = $cell->getAttribute('bgcolor');
if (defined $bgcolor) {
$cell->removeAttribute('bgcolor');
if (!defined $old_properties->{'background-color'}) {
$bgcolor = trim($bgcolor);
$bgcolor =~ s/^x\s*//;
if ($bgcolor ne '') {
$new_properties{'background-color'} = $bgcolor;
}
}
}
my $align = $cell->getAttribute('align');
if (defined $align && $align !~ /\s*char\s*/i) {
$cell->removeAttribute('align');
if (!defined $old_properties->{'text-align'}) {
$align = lc(trim($align));
if ($align ne '') {
$new_properties{'text-align'} = $align;
}
}
}
my $valign = $cell->getAttribute('valign');
if (defined $valign) {
$cell->removeAttribute('valign');
if (!defined $old_properties->{'vertical-align'}) {
$valign = lc(trim($valign));
if ($valign ne '') {
$new_properties{'vertical-align'} = $valign;
}
}
}
if (scalar(keys %new_properties) > 0) {
set_css_properties($cell, \%new_properties);
}
}
}
# Replaces deprecated attributes in lists (ul and ol)
sub fix_deprecated_in_lists {
my ($root) = @_;
my @uls = $root->getElementsByTagName('ul');
my @ols = $root->getElementsByTagName('ol');
my @lists = (@uls, @ols);
foreach my $list (@lists) {
my $type = $list->getAttribute('type');
if (defined $type) {
my $lst = list_style_type($type);
if (defined $lst) {
$list->removeAttribute('type');
if (!defined get_css_property($list, 'list-style-type')) {
set_css_property($list, 'list-style-type', $lst);
}
}
}
}
}
# Replaces deprecated attributes in list items (li)
sub fix_deprecated_in_list_items {
my ($root) = @_;
my @lis = $root->getElementsByTagName('li');
foreach my $li (@lis) {
my $type = $li->getAttribute('type');
if (defined $type) {
my $lst = list_style_type($type);
if (defined $lst) {
$li->removeAttribute('type');
if (!defined get_css_property($li, 'list-style-type')) {
set_css_property($li, 'list-style-type', $lst);
}
}
}
}
}
# returns the CSS list-style-type value equivalent to the given type attribute for a list or list item
sub list_style_type {
my ($type) = @_;
my $value;
$type = trim($type);
if (lc($type) eq 'circle') {
$value = 'circle';
} elsif (lc($type) eq 'disc') {
$value = 'disc';
} elsif (lc($type) eq 'square') {
$value = 'square';
} elsif ($type eq 'a') {
$value = 'lower-latin';
} elsif ($type eq 'A') {
$value = 'upper-latin';
} elsif ($type eq 'i') {
$value = 'lower-roman';
} elsif ($type eq 'I') {
$value = 'upper-roman';
} elsif ($type eq '1') {
$value = 'decimal';
}
return $value;
}
# Replaces deprecated attributes in hr
sub fix_deprecated_in_hr {
my ($root) = @_;
my @hrs = $root->getElementsByTagName('hr');
foreach my $hr (@hrs) {
tie (my %new_properties, 'Tie::IxHash', ());
my $align = $hr->getAttribute('align');
if (defined $align) {
$align = lc(trim($align));
if ($align eq 'left') {
$new_properties{'text-align'} = 'left';
$new_properties{'margin-left'} = '0';
} elsif ($align eq 'right') {
$new_properties{'text-align'} = 'right';
$new_properties{'margin-right'} = '0';
}
$hr->removeAttribute('align');
}
my $color = $hr->getAttribute('color');
if (defined $color) {
$color = trim($color);
$color =~ s/^x\s*//;
if ($color ne '') {
$new_properties{'color'} = $color;
$new_properties{'background-color'} = $color;
}
$hr->removeAttribute('color');
}
my $noshade = $hr->getAttribute('noshade');
my $size = $hr->getAttribute('size');
if (defined $noshade) {
$new_properties{'border-width'} = '0';
if (!defined $color) {
$new_properties{'color'} = 'gray';
$new_properties{'background-color'} = 'gray';
}
if (!defined $size) {
$size = '2';
}
$hr->removeAttribute('noshade');
}
if (defined $size) {
$size = trim($size);
if ($size ne '') {
$new_properties{'height'} = $size.'px';
}
if (defined $hr->getAttribute('size')) {
$hr->removeAttribute('size');
}
}
my $width = $hr->getAttribute('width');
if (defined $width) {
$width = trim($width);
if ($width ne '') {
if ($width !~ /\%$/) {
$width .= 'px';
}
$new_properties{'width'} = $width;
}
$hr->removeAttribute('width');
}
if (scalar(keys %new_properties) > 0) {
set_css_properties($hr, \%new_properties);
}
}
}
# Replaces deprecated attributes in img
sub fix_deprecated_in_img {
my ($root) = @_;
my @imgs = $root->getElementsByTagName('img');
foreach my $img (@imgs) {
my $old_properties = get_css_properties($img);
tie (my %new_properties, 'Tie::IxHash', ());
my $align = $img->getAttribute('align');
if (defined $align) {
$align = lc(trim($align));
if ($align eq 'middle' || $align eq 'top' || $align eq 'bottom') {
$img->removeAttribute('align');
if (!defined $old_properties->{'vertical-align'}) {
$new_properties{'vertical-align'} = $align;
}
} elsif ($align eq 'left' || $align eq 'right') {
$img->removeAttribute('align');
if (!defined $old_properties->{'float'}) {
$new_properties{'float'} = $align;
}
} elsif ($align eq 'center' || $align eq '') {
$img->removeAttribute('align');
}
}
my $border = $img->getAttribute('border');
if (defined $border) {
$border = lc(trim($border));
if ($border =~ /^[0-9]+\s*(px)?$/) {
$img->removeAttribute('border');
if (!defined $old_properties->{'border'}) {
if ($border !~ /px$/) {
$border .= 'px';
}
$new_properties{'border'} = $border.' solid black';
}
}
}
my $hspace = $img->getAttribute('hspace');
if (defined $hspace) {
$hspace = lc(trim($hspace));
if ($hspace =~ /^[0-9]+\s*(px)?$/) {
$img->removeAttribute('hspace');
if (!defined $old_properties->{'margin-left'} || !defined $old_properties->{'margin-right'}) {
if ($hspace !~ /px$/) {
$hspace .= 'px';
}
$new_properties{'margin-left'} = $hspace;
$new_properties{'margin-right'} = $hspace;
}
}
}
if (scalar(keys %new_properties) > 0) {
set_css_properties($img, \%new_properties);
}
}
}
# Replaces deprecated attributes in htmlbody (the style attribute could be used in a div for output)
sub fix_deprecated_in_body {
my ($root) = @_;
my $doc = $root->ownerDocument;
my @bodies = $root->getElementsByTagName('htmlbody');
foreach my $body (@bodies) {
my $old_properties = get_css_properties($body);
tie (my %new_properties, 'Tie::IxHash', ());
my $bgcolor = $body->getAttribute('bgcolor');
if (defined $bgcolor) {
$body->removeAttribute('bgcolor');
if (!defined $old_properties->{'background-color'}) {
$bgcolor = trim($bgcolor);
$bgcolor =~ s/^x\s*//;
if ($bgcolor ne '') {
$new_properties{'background-color'} = $bgcolor;
}
}
}
my $color = $body->getAttribute('text');
if (defined $color) {
$body->removeAttribute('text');
if (!defined $old_properties->{'color'}) {
$color = trim($color);
$color =~ s/^x\s*//;
if ($color ne '') {
$new_properties{'color'} = $color;
}
}
}
my $background = $body->getAttribute('background');
if (defined $background && ($background =~ /\.jpe?g$|\.gif|\.png/i)) {
$body->removeAttribute('background');
if (!defined $old_properties->{'background-image'}) {
$background = trim($background);
if ($background ne '') {
$new_properties{'background-image'} = 'url('.$background.')';
}
}
}
# NOTE: these attributes have never been standard and are better removed with no replacement
foreach my $bad ('bottommargin', 'leftmargin', 'rightmargin', 'topmargin', 'marginheight', 'marginwidth') {
if ($body->hasAttribute($bad)) {
$body->removeAttribute($bad);
}
}
# NOTE: link alink and vlink require a <style> block to be converted
my $link = $body->getAttribute('link');
my $alink = $body->getAttribute('alink');
my $vlink = $body->getAttribute('vlink');
if (defined $link || defined $alink || defined $vlink) {
my $head;
my @heads = $root->getElementsByTagName('htmlhead');
if (scalar(@heads) > 0) {
$head = $heads[0];
} else {
$head = $doc->createElement('htmlhead');
$root->insertBefore($head, $root->firstChild);
}
my $style = $doc->createElement('style');
$head->appendChild($style);
my $css = "\n";
if (defined $link) {
$body->removeAttribute('link');
$link = trim($link);
$link =~ s/^x\s*//;
$css .= ' a:link { color:'.$link.' }';
$css .= "\n";
}
if (defined $alink) {
$body->removeAttribute('alink');
$alink = trim($alink);
$alink =~ s/^x\s*//;
$css .= ' a:active { color:'.$alink.' }';
$css .= "\n";
}
if (defined $vlink) {
$body->removeAttribute('vlink');
$vlink = trim($vlink);
$vlink =~ s/^x\s*//;
$css .= ' a:visited { color:'.$vlink.' }';
$css .= "\n";
}
$css .= ' ';
$style->appendChild($doc->createTextNode($css));
}
if (scalar(keys %new_properties) > 0) {
set_css_properties($body, \%new_properties);
} elsif (!$body->hasAttributes) {
$body->parentNode->removeChild($body);
}
}
}
# replaces <div align="center"> by <div style="text-align:center;">
# also for p and h1..h6
sub fix_align_attribute {
my ($root) = @_;
my @nodes = $root->getElementsByTagName('div');
push(@nodes, $root->getElementsByTagName('p'));
for (my $i=1; $i<=6; $i++) {
push(@nodes, $root->getElementsByTagName('h'.$i));
}
foreach my $node (@nodes) {
my $align = $node->getAttribute('align');
if (defined $align) {
$node->removeAttribute('align');
$align = trim($align);
if ($align ne '' && !defined get_css_property($node, 'text-align')) {
set_css_property($node, 'text-align', lc($align));
}
}
}
}
# replace center by a div or remove it if there is a table inside
sub replace_center {
my ($root, $all_block) = @_;
my $doc = $root->ownerDocument;
my @centers = $root->getElementsByTagName('center');
foreach my $center (@centers) {
if ($center->getChildrenByTagName('table')->size() > 0) { # note: getChildrenByTagName is not DOM (LibXML specific)
replace_by_children($center);
} else {
if ((!defined $center->previousSibling ||
($center->previousSibling->nodeType == XML_TEXT_NODE && $center->previousSibling->nodeValue =~ /^\s*$/ && !defined $center->previousSibling->previousSibling)) &&
(!defined $center->nextSibling ||
($center->nextSibling->nodeType == XML_TEXT_NODE && $center->nextSibling->nodeValue =~ /^\s*$/ && !defined $center->nextSibling->nextSibling)) &&
string_in_array(\@accepting_style, $center->parentNode->nodeName)) {
# use CSS on the parent block and replace center by its children
set_css_property($center->parentNode, 'text-align', 'center');
replace_by_children($center);
} else {
# use p or div ? check if there is a block inside
my $found_block = 0;
for (my $child=$center->firstChild; defined $child; $child=$child->nextSibling) {
if ($child->nodeType == XML_ELEMENT_NODE && string_in_array($all_block, $child->nodeName)) {
$found_block = 1;
last;
}
}
my $new_node;
if ($found_block) {
$new_node = $doc->createElement('div');
$new_node->setAttribute('style', 'text-align: center; margin: 0 auto');
} else {
$new_node = $doc->createElement('p');
$new_node->setAttribute('style', 'text-align: center');
}
my $next;
for (my $child=$center->firstChild; defined $child; $child=$next) {
$next = $child->nextSibling;
$center->removeChild($child);
$new_node->appendChild($child);
}
$center->parentNode->replaceChild($new_node, $center);
}
}
}
}
# replaces <nobr> by <span style="white-space:nowrap">
sub replace_nobr {
my ($root) = @_;
my @nobrs = $root->getElementsByTagName('nobr');
foreach my $nobr (@nobrs) {
if (!defined $nobr->previousSibling && !defined $nobr->nextSibling &&
string_in_array(\@accepting_style, $nobr->parentNode->nodeName)) {
# use CSS on the parent block
set_css_property($nobr->parentNode, 'white-space', 'nowrap');
replace_by_children($nobr);
} else {
$nobr->setNodeName('span');
$nobr->setAttribute('style', 'white-space:nowrap');
}
}
}
# removes notsolved tags in the case <hintgroup showoncorrect="no"><notsolved>...</notsolved></hintgroup>
# and in the case <notsolved><hintgroup showoncorrect="no">...</hintgroup></notsolved>
sub remove_useless_notsolved {
my ($root) = @_;
my @hintgroups = $root->getElementsByTagName('hintgroup');
foreach my $hintgroup (@hintgroups) {
my $showoncorrect = get_non_empty_attribute($hintgroup, 'showoncorrect');
if (!defined $showoncorrect || $showoncorrect eq 'no') {
my @notsolveds = $hintgroup->getElementsByTagName('notsolved');
foreach my $notsolved (@notsolveds) {
replace_by_children($notsolved);
}
}
my $parent = $hintgroup->parentNode;
if ($parent->nodeName eq 'notsolved' && scalar(@{$parent->nonBlankChildNodes()}) == 1) {
replace_by_children($parent);
}
}
}
# adds a paragraph inside if needed and calls fix_paragraph for all paragraphs (including new ones)
sub fix_paragraphs_inside {
my ($node, $all_block) = @_;
# blocks in which paragrahs will be added:
my @blocks_with_p = ('loncapa','library','problem','part','problemtype','window','block','while','postanswerdate','preduedate','solved','notsolved','languageblock','translated','lang','instructorcomment','togglebox','standalone','form');
my @fix_p_if_br_or_p = (@responses,'foil','item','text','label','hintgroup','hintpart','hint','web','windowlink','div','li','dd','td','th','blockquote');
if ((string_in_array(\@blocks_with_p, $node->nodeName) && paragraph_needed($node)) ||
(string_in_array(\@fix_p_if_br_or_p, $node->nodeName) && paragraph_inside($node))) {
# if non-empty, add paragraphs where needed between all br and remove br
# (it would be easier to just put everything in a p and fix it afterwards, but there are performance issues
# when a paragraph has many blocks directly inside)
my $doc = $node->ownerDocument;
my $p = undef;
my @new_children = ();
my $next;
for (my $child=$node->firstChild; defined $child; $child=$next) {
$next = $child->nextSibling;
$node->removeChild($child);
if ($child->nodeType == XML_ELEMENT_NODE && $child->nodeName eq 'br') {
if (defined $p) {
push(@new_children, $p);
} else {
push(@new_children, $doc->createElement('p'));
}
$p = undef;
} elsif ($child->nodeType == XML_ELEMENT_NODE && string_in_array(\@inline_like_block, $child->nodeName)) {
# inline_like_block: use the paragraph if there is one, otherwise do not create one
if (defined $p) {
$p->appendChild($child);
} else {
push(@new_children, $child);
}
} elsif ($child->nodeType == XML_ELEMENT_NODE && string_in_array($all_block, $child->nodeName)) {
# these children are blocks and should not be in a paragraph
if (defined $p) {
push(@new_children, $p);
$p = undef;
}
push(@new_children, $child);
} elsif ($child->nodeType == XML_TEXT_NODE && $child->nodeValue =~ /^[ \t\f\n\r]*$/) {
# blank text: add to paragraph if there is one and there is a next node, otherwise keep out of the paragraph
if (defined $p) {
if (defined $next) {
$p->appendChild($child);
} else {
push(@new_children, $p);
$p = undef;
push(@new_children, $child);
}
} else {
push(@new_children, $child);
}
} elsif ($child->nodeType == XML_TEXT_NODE ||
$child->nodeType == XML_ELEMENT_NODE || $child->nodeType == XML_CDATA_SECTION_NODE ||
$child->nodeType == XML_ENTITY_NODE || $child->nodeType == XML_ENTITY_REF_NODE) {
# these children require a paragraph
if (!defined $p) {
$p = $doc->createElement('p');
}
$p->appendChild($child);
} else {
# these children do not require a paragraph (XML comments, PI)
# -> do not move them in a new paragraph
if (defined $p) {
push(@new_children, $p);
$p = undef;
}
push(@new_children, $child);
}
}
if (defined $p) {
push(@new_children, $p);
}
foreach my $child (@new_children) {
$node->appendChild($child);
}
}
# now fix the paragraphs everywhere, so that all inline nodes are inside a paragraph, and block nodes are outside
my $next;
for (my $child=$node->firstChild; defined $child; $child=$next) {
$next = $child->nextSibling;
if ($child->nodeType == XML_ELEMENT_NODE && defined $child->firstChild) {
if ($child->nodeName eq 'p') {
fix_paragraph($child, $all_block);
} else {
fix_paragraphs_inside($child, $all_block);
}
}
}
}
# returns 1 if a paragraph is needed inside this node (assuming the parent can have paragraphs)
sub paragraph_needed {
my ($node) = @_;
for (my $child=$node->firstChild; defined $child; $child=$child->nextSibling) {
if (($child->nodeType == XML_TEXT_NODE && $child->nodeValue !~ /^\s*$/) ||
($child->nodeType == XML_ELEMENT_NODE && !string_in_array(\@inline_like_block, $child->nodeName)) ||
$child->nodeType == XML_CDATA_SECTION_NODE ||
$child->nodeType == XML_ENTITY_NODE || $child->nodeType == XML_ENTITY_REF_NODE) {
return(1);
}
}
return(0);
}
# returns 1 if there is a paragraph or br in a child of this node, or inside an inline child
sub paragraph_inside {
my ($node) = @_;
# inline elements that can be split in half if there is a paragraph inside (currently all HTML):
# (also used in first_block below)
my @splitable_inline = ('span', 'a', 'strong', 'em' , 'b', 'i', 'sup', 'sub', 'code', 'kbd', 'samp', 'tt', 'ins', 'del', 'var', 'small', 'big', 'font', 'u');
for (my $child=$node->firstChild; defined $child; $child=$child->nextSibling) {
if ($child->nodeType == XML_ELEMENT_NODE) {
my $name = $child->nodeName;
if ($name eq 'p' || $name eq 'br') {
return(1);
} elsif (string_in_array(\@splitable_inline, $name)) {
if (paragraph_inside($child)) {
return(1);
}
}
}
}
return(0);
}
# fixes paragraphs inside paragraphs (without a block in-between)
sub fix_paragraph {
my ($p, $all_block) = @_;
my $loop_right = 1; # this loops is to avoid out of memory errors with recurse, see below
while ($loop_right) {
$loop_right = 0;
my $block = find_first_block($p, $all_block);
if (defined $block) {
my $trees = clone_ancestor_around_node($p, $block);
my $doc = $p->ownerDocument;
my $replacement = $doc->createDocumentFragment();
my $left = $trees->{'left'};
my $middle = $trees->{'middle'};
my $right = $trees->{'right'};
my $left_needs_p = 0; # 1 if it needs a paragraph (used to replace br later)
if (defined $left) {
# fix paragraphs inside, in case one of the descendants can have paragraphs inside (like numericalresponse/hintgroup):
for (my $child=$left->firstChild; defined $child; $child=$child->nextSibling) {
if ($child->nodeType == XML_ELEMENT_NODE) {
fix_paragraphs_inside($child, $all_block);
}
}
if (!paragraph_needed($left)) {
# this was just blank text, comments or inline responses, it should not create a new paragraph
my $next;
for (my $child=$left->firstChild; defined $child; $child=$next) {
$next = $child->nextSibling;
$left->removeChild($child);
$replacement->appendChild($child);
}
} else {
$left_needs_p = 1;
$replacement->appendChild($left);
}
}
my $n = $middle->firstChild;
while (defined $n) {
if ($n->nodeType == XML_ELEMENT_NODE && (string_in_array($all_block, $n->nodeName) || $n->nodeName eq 'br')) {
if ($n->nodeName eq 'p') {
my $parent = $n->parentNode;
# first apply recursion
fix_paragraph($n, $all_block);
# now the p might have been replaced by several nodes, which should replace the initial p
my $next_block;
for (my $block=$parent->firstChild; defined $block; $block=$next_block) {
$next_block = $block->nextSibling;
if ($block->nodeName eq 'p') {
$parent->removeChild($block);
# for each parent before $middle, clone in-between the p and its children (to preserve the styles)
if (defined $block->firstChild) {
for (my $p=$parent; $p!=$middle; $p=$p->parentNode) {
my $newp = $p->cloneNode(0);
my $next;
for (my $child=$block->firstChild; defined $child; $child=$next) {
$next = $child->nextSibling;
$block->removeChild($child);
$newp->appendChild($child);
}
$block->appendChild($newp);
}
}
}
$replacement->appendChild($block);
}
} else {
# replace the whole p by this block, forgetting about intermediate inline elements
$n->parentNode->removeChild($n);
if ($n->nodeName eq 'br') {
# replace a br by a paragraph if there was nothing before in the paragraph,
# otherwise remove it because it already broke the paragraph in half
if (!defined $left || !$left_needs_p) {
$replacement->appendChild($middle);
}
} else {
fix_paragraphs_inside($n, $all_block);
$replacement->appendChild($n);
}
}
last;
}
$n = $n->firstChild;
if (defined $n && defined $n->nextSibling) {
die "Error in post_xml.fix_paragraph: block not found";
}
}
if (defined $right) {
if ($block->nodeName eq 'p') {
# remove attributes on the right paragraph
my @attributelist = $right->attributes();
foreach my $att (@attributelist) {
$right->removeAttribute($att->nodeName);
}
}
if ($right->firstChild->nodeType == XML_TEXT_NODE && $right->firstChild->nodeValue =~ /^[ \t\f\n\r]*$/) {
# remove the first text node with whitespace only from the p, it should not trigger the creation of a p
# (but take nbsp into account, so we should not use \s here)
my $first = $right->firstChild;
$right->removeChild($first);
$replacement->appendChild($first);
}
if (defined $right->firstChild) {
if (paragraph_needed($right)) {
$replacement->appendChild($right);
#fix_paragraph($right, $all_block); This is taking way too much memory for blocks with many children
# -> loop instead of recurse
$loop_right = 1;
} else {
# this was just blank text, comments or inline responses, it should not create a new paragraph
my $next;
for (my $child=$right->firstChild; defined $child; $child=$next) {
$next = $child->nextSibling;
$right->removeChild($child);
$replacement->appendChild($child);
# fix paragraphs inside, in case one of the descendants can have paragraphs inside (like numericalresponse/hintgroup):
if ($child->nodeType == XML_ELEMENT_NODE) {
fix_paragraphs_inside($child, $all_block);
}
}
}
}
}
$p->parentNode->replaceChild($replacement, $p);
if ($loop_right) {
$p = $right;
}
} else {
# fix paragraphs inside, in case one of the descendants can have paragraphs inside (like numericalresponse/hintgroup):
my $next;
for (my $child=$p->firstChild; defined $child; $child=$next) {
$next = $child->nextSibling;
if ($child->nodeType == XML_ELEMENT_NODE) {
fix_paragraphs_inside($child, $all_block);
}
}
}
}
}
sub find_first_block {
my ($node, $all_block) = @_;
# inline elements that can be split in half if there is a paragraph inside (currently all HTML):
my @splitable_inline = ('span', 'a', 'strong', 'em' , 'b', 'i', 'sup', 'sub', 'code', 'kbd', 'samp', 'tt', 'ins', 'del', 'var', 'small', 'big', 'font', 'u');
for (my $child=$node->firstChild; defined $child; $child=$child->nextSibling) {
if ($child->nodeType == XML_ELEMENT_NODE) {
if (string_in_array($all_block, $child->nodeName) || $child->nodeName eq 'br') {
return($child);
}
if (string_in_array(\@splitable_inline, $child->nodeName)) {
my $block = find_first_block($child, $all_block);
if (defined $block) {
return($block);
}
}
}
}
return(undef);
}
# Creates clones of the ancestor containing the descendants before the node, at the node, and after the node.
# returns a hash with: left, middle, right (left and right can be undef)
sub clone_ancestor_around_node {
my ($ancestor, $node) = @_;
my $middle_node;
my ($left, $middle, $right);
for (my $child=$ancestor->firstChild; defined $child; $child=$child->nextSibling) {
if ($child == $node || is_ancestor_of($child, $node)) {
$middle_node = $child;
last;
}
}
if (!defined $middle_node) {
die "error in split_ancestor_around_node: middle not found";
}
if (defined $middle_node->previousSibling) {
$left = $ancestor->cloneNode(0);
for (my $child=$ancestor->firstChild; $child != $middle_node; $child=$child->nextSibling) {
$left->appendChild($child->cloneNode(1));
}
}
$middle = $ancestor->cloneNode(0);
if ($middle_node == $node) {
$middle->appendChild($middle_node->cloneNode(1));
} else {
my $subres = clone_ancestor_around_node($middle_node, $node);
my $subleft = $subres->{'left'};
if (defined $subleft) {
if (!defined $left) {
$left = $ancestor->cloneNode(0);
}
$left->appendChild($subleft);
}
$middle->appendChild($subres->{'middle'});
my $subright = $subres->{'right'};
if (defined $subright) {
$right = $ancestor->cloneNode(0);
$right->appendChild($subright);
}
}
if (defined $middle_node->nextSibling) {
if (!defined $right) {
$right = $ancestor->cloneNode(0);
}
for (my $child=$middle_node->nextSibling; defined $child; $child=$child->nextSibling) {
$right->appendChild($child->cloneNode(1));
}
}
my %result = ();
$result{'left'} = $left;
$result{'middle'} = $middle;
$result{'right'} = $right;
return(\%result);
}
sub is_ancestor_of {
my ($n1, $n2) = @_;
my $n = $n2->parentNode;
while (defined $n) {
if ($n == $n1) {
return(1);
}
$n = $n->parentNode;
}
return(0);
}
# removes empty style elements and replaces the ones with only whitespaces inside by their content
# also remove hints that have become empty after empty style removal.
sub remove_empty_style {
my ($root) = @_;
# actually, preserve some elements like ins when they have whitespace, only remove if they are empty
my @remove_if_empty = ('span', 'strong', 'em' , 'b', 'i', 'sup', 'sub', 'code', 'kbd', 'samp', 'tt', 'ins', 'del', 'var', 'small', 'big', 'font', 'u', 'hint');
my @remove_if_blank = ('span', 'strong', 'em' , 'b', 'i', 'sup', 'sub', 'tt', 'var', 'small', 'big', 'font', 'u', 'hint');
foreach my $name (@remove_if_empty) {
my @nodes = $root->getElementsByTagName($name);
while (scalar(@nodes) > 0) {
my $node = pop(@nodes);
if (!defined $node->firstChild) {
my $parent = $node->parentNode;
if (defined $node->previousSibling && $node->previousSibling->nodeType == XML_TEXT_NODE &&
$node->previousSibling->nodeValue =~ /\$\S*$/) {
# case $a<sup></sup>x
my $value = $node->previousSibling->nodeValue;
$value =~ s/\$(\S*)$/\$\{$1\}/;
$node->previousSibling->setData($value);
}
$parent->removeChild($node);
$parent->normalize();
# now that we removed the node, check if the parent has become an empty style, and so on
while (defined $parent && string_in_array(\@remove_if_empty, $parent->nodeName) && !defined $parent->firstChild) {
my $grandparent = $parent->parentNode;
$grandparent->removeChild($parent);
remove_reference_from_array(\@nodes, $parent);
$parent = $grandparent;
}
}
}
}
foreach my $name (@remove_if_blank) {
my @nodes = $root->getElementsByTagName($name);
while (scalar(@nodes) > 0) {
my $node = pop(@nodes);
if (defined $node->firstChild && !defined $node->firstChild->nextSibling && $node->firstChild->nodeType == XML_TEXT_NODE) {
# NOTE: careful, with UTF-8, \s matches non-breaking spaces and we want to preserve these
if ($node->firstChild->nodeValue =~ /^[\t\n\f\r ]*$/) {
my $parent = $node->parentNode;
replace_by_children($node);
$parent->normalize();
# now that we removed the node, check if the parent has become a style with only whitespace, and so on
while (defined $parent && string_in_array(\@remove_if_blank, $parent->nodeName) &&
(!defined $parent->firstChild ||
(!defined $parent->firstChild->nextSibling && $parent->firstChild->nodeType == XML_TEXT_NODE &&
$parent->firstChild->nodeValue =~ /^^[\t\n\f\r ]*$/))) {
my $grandparent = $parent->parentNode;
replace_by_children($parent);
remove_reference_from_array(\@nodes, $parent);
$parent = $grandparent;
}
}
}
}
}
}
# remove whitespace inside LON-CAPA elements that have an empty content-model (HTML ones are handled by html_to_xml)
sub fix_empty_lc_elements {
my ($node) = @_;
my @lcempty = ('arc','axis','backgroundplot','drawoptionlist','drawvectorsum','fill','functionplotrule','functionplotvectorrule','functionplotvectorsumrule','hiddenline','hiddensubmission','key','line','location','organicstructure','parameter','plotobject','plotvector','responseparam','spline','textline');
if (string_in_array(\@lcempty, $node->nodeName)) {
if (defined $node->firstChild && !defined $node->firstChild->nextSibling &&
$node->firstChild->nodeType == XML_TEXT_NODE && $node->firstChild->nodeValue =~ /^\s*$/) {
$node->removeChild($node->firstChild);
}
if (defined $node->firstChild) {
print "Warning: a ".$node->nodeName." has something inside\n";
}
return;
}
for (my $child=$node->firstChild; defined $child; $child=$child->nextSibling) {
if ($child->nodeType == XML_ELEMENT_NODE) {
fix_empty_lc_elements($child);
}
}
}
# turn some attribute values into lowercase when they should be
sub lowercase_attribute_values {
my ($root) = @_;
my @with_yesno = (['radiobuttonresponse', ['randomize']],
['optionresponse', ['randomize']],
['matchresponse', ['randomize']],
['itemgroup', ['randomize']],
['rankresponse', ['randomize']],
['functionplotresponse', ['xaxisvisible', 'yaxisvisible', 'gridvisible']],
['backgroundplot', ['fixed']],
['drawvectorsum', ['showvalue']],
['textline', ['readonly']],
['hint', ['showoncorrect']],
['body', ['dir']],
['img', ['encrypturl']],
['form', ['method']],
['input', ['type']]
);
foreach my $el_attributes (@with_yesno) {
my $el_name = $el_attributes->[0];
my @elements = $root->getElementsByTagName($el_name);
foreach my $element (@elements) {
my $att_list = $el_attributes->[1];
foreach my $att_name (@$att_list) {
my $att_value = $element->getAttribute($att_name);
if (!defined $att_value) {
next;
}
if ($att_value eq 'yes' || $att_value eq 'no') {
next;
}
if ($att_value =~ /\s*yes\s*/i) {
$element->setAttribute($att_name, 'yes');
} elsif ($att_value =~ /\s*no\s*/i) {
$element->setAttribute($att_name, 'no');
}
}
}
}
}
# fixes spelling mistakes for numericalresponse/@unit
sub replace_numericalresponse_unit_attribute {
my ($root) = @_;
my @numericalresponses = $root->getElementsByTagName('numericalresponse');
foreach my $numericalresponse (@numericalresponses) {
if (defined $numericalresponse->getAttribute('units') && !defined $numericalresponse->getAttribute('unit')) {
$numericalresponse->setAttribute('unit', $numericalresponse->getAttribute('units'));
$numericalresponse->removeAttribute('units');
}
}
}
# Replaces &format and &prettyprint by <num> whenever possible.
# Also replaces &chemparse by <chem>.
# If the function call is enclosed in <display>, the <display> element is removed.
sub replace_functions_by_elements {
my ($root) = @_;
my $doc = $root->ownerDocument;
my @preserve = ('script','answer','parse','m','tm','dtm','numericalhintscript'); # display is handled later
my @all = $root->getElementsByTagName('*');
foreach my $element (@all) {
if (string_in_array(\@preserve, $element->nodeName)) {
next;
}
my $changed = 0;
my $next;
for (my $child=$element->firstChild; defined $child; $child=$next) {
$next = $child->nextSibling;
if ($child->nodeType == XML_TEXT_NODE) {
my $value = $child->nodeValue;
if ($value =~ /^(.*)&(?:format|prettyprint)\((\$\{?[a-zA-Z0-9]*\}?(?:\[[^\]]*\])?|[0-9.]+)\s?,\s?(["'][,.\$]?[0-9][eEfFgGsS]["']|\$[a-zA-Z0-9]*)\)(.*)$/s) {
# NOTE: we don't check for &prettyprint's 3rd argument (target), but it has not been seen outside of script elements.
# NOTE: the format options ',' and '$' are not supported by &format in current LON-CAPA since rev 1.81 of default_homework.lcpm,
# but are supported by &prettyprint;
# if we use (like current LON-CAPA) &prettyprint for <num> implementation, it will change a few resulting documents
# (by making them display something they were probably intended to display, but which did not).
# Usage of <num> with &prettyprint instead of &format might also change the display when there is an exponent.
my $before = $1;
my $number = $2;
my $format = $3;
my $after = $4;
$format =~ s/^['"]|['"]$//g;
# do not change this if the parent is <display> and there are other things before or after &format
if ($element->nodeName eq 'display' && (defined $child->previousSibling || defined $next ||
$before !~ /^\s*$/ || $after !~ /^\s*$/)) {
last;
}
my $replacement = $doc->createDocumentFragment();
my $num = $doc->createElement('num');
$num->setAttribute('format', $format);
$num->appendChild($doc->createTextNode($number));
if (length($before) > 0) {
$replacement->appendChild($doc->createTextNode($before));
}
$replacement->appendChild($num);
if (length($after) > 0) {
$replacement->appendChild($doc->createTextNode($after));
}
$element->replaceChild($replacement, $child);
$changed = 1;
$next = $element->firstChild; # start over, there might be another &format in the same text node
} elsif ($value =~ /^(.*)&chemparse\(([^'"()]*|'[^']*'|"[^"]*")\)(.*)$/s) {
my $before = $1;
my $reaction = $2;
my $after = $3;
$reaction =~ s/^'(.*)'$/$1/;
$reaction =~ s/^"(.*)"$/$1/;
if ($element->nodeName eq 'display' && (defined $child->previousSibling || defined $next ||
$before !~ /^\s*$/ || $after !~ /^\s*$/)) {
last;
}
my $replacement = $doc->createDocumentFragment();
my $chem = $doc->createElement('chem');
$chem->appendChild($doc->createTextNode($reaction));
if (length($before) > 0) {
$replacement->appendChild($doc->createTextNode($before));
}
$replacement->appendChild($chem);
if (length($after) > 0) {
$replacement->appendChild($doc->createTextNode($after));
}
$element->replaceChild($replacement, $child);
$changed = 1;
$next = $element->firstChild;
}
}
}
if ($changed && $element->nodeName eq 'display') {
my $first = $element->firstChild;
if ($first->nodeType == XML_ELEMENT_NODE && string_in_array(['num','chem'], $first->nodeName) &&
!defined $first->nextSibling) {
# remove useless display element
replace_by_children($element);
}
}
}
}
# pretty-print using im-memory DOM tree
sub pretty {
my ($node, $all_block, $indent_level) = @_;
my $doc = $node->ownerDocument;
$indent_level ||= 0;
my $type = $node->nodeType;
if ($type == XML_ELEMENT_NODE) {
my $name = $node->nodeName;
if ((string_in_array($all_block, $name) || string_in_array(\@inline_like_block, $name)) &&
!string_in_array(\@preserve_elements, $name)) {
# make sure there is a newline at the beginning and at the end if there is anything inside
if (defined $node->firstChild && !string_in_array(\@no_newline_inside, $name)) {
my $first = $node->firstChild;
if ($first->nodeType == XML_TEXT_NODE) {
my $text = $first->nodeValue;
if ($text !~ /^ *\n/) {
$first->setData("\n" . $text);
}
} else {
$node->insertBefore($doc->createTextNode("\n"), $first);
}
my $last = $node->lastChild;
if ($last->nodeType == XML_TEXT_NODE) {
my $text = $last->nodeValue;
if ($text !~ /\n *$/) {
$last->setData($text . "\n");
}
} else {
$node->appendChild($doc->createTextNode("\n"));
}
}
# indent and make sure there is a newline before and after a block element
my $newline_indent = "\n".(' ' x (2*($indent_level + 1)));
my $newline_indent_last = "\n".(' ' x (2*$indent_level));
my $next;
for (my $child=$node->firstChild; defined $child; $child=$next) {
$next = $child->nextSibling;
if ($child->nodeType == XML_ELEMENT_NODE) {
if (string_in_array($all_block, $child->nodeName) || string_in_array(\@inline_like_block, $child->nodeName)) {
# make sure there is a newline before and after a block element
if (defined $child->previousSibling && $child->previousSibling->nodeType == XML_TEXT_NODE) {
my $prev = $child->previousSibling;
my $text = $prev->nodeValue;
if ($text !~ /\n *$/) {
$prev->setData($text . $newline_indent);
}
} else {
$node->insertBefore($doc->createTextNode($newline_indent), $child);
}
if (defined $next && $next->nodeType == XML_TEXT_NODE) {
my $text = $next->nodeValue;
if ($text !~ /^ *\n/) {
$next->setData($newline_indent . $text);
}
} else {
$node->insertAfter($doc->createTextNode($newline_indent), $child);
}
}
pretty($child, $all_block, $indent_level+1);
} elsif ($child->nodeType == XML_TEXT_NODE) {
my $text = $child->nodeValue;
# collapse newlines
$text =~ s/\n([\t ]*\n)+/\n/g;
# indent and remove spaces and tabs before newlines
if (defined $next) {
$text =~ s/[\t ]*\n[\t ]*/$newline_indent/ge;
} else {
$text =~ s/[\t ]*\n[\t ]*/$newline_indent/ge;
$text =~ s/[\t ]*\n[\t ]*$/$newline_indent_last/e;
}
$child->setData($text);
}
}
# removes whitespace at the beginning and end of p td, th and li (except for nbsp at the beginning)
my @to_trim = ('p','td','th','li');
if (string_in_array(\@to_trim, $name) && defined $node->firstChild && $node->firstChild->nodeType == XML_TEXT_NODE) {
my $text = $node->firstChild->nodeValue;
$text =~ s/^[ \t\f\n\r]*//;
if ($text eq '') {
$node->removeChild($node->firstChild);
} else {
$node->firstChild->setData($text);
}
}
if (string_in_array(\@to_trim, $name) && defined $node->lastChild && $node->lastChild->nodeType == XML_TEXT_NODE) {
my $text = $node->lastChild->nodeValue;
$text =~ s/\s*$//;
if ($text eq '') {
$node->removeChild($node->lastChild);
} else {
$node->lastChild->setData($text);
}
}
} elsif (string_in_array(\@preserve_elements, $name)) {
# collapse newlines at the beginning and the end of scripts
if (defined $node->firstChild && $node->firstChild->nodeType == XML_TEXT_NODE) {
my $text = $node->firstChild->nodeValue;
$text =~ s/^\n( *\n)+/\n/;
if ($text eq '') {
$node->removeChild($node->firstChild);
} else {
$node->firstChild->setData($text);
}
}
if (defined $node->lastChild && $node->lastChild->nodeType == XML_TEXT_NODE) {
my $text = $node->lastChild->nodeValue;
$text =~ s/\n( *\n)+$/\n/;
if ($text eq '') {
$node->removeChild($node->lastChild);
} else {
$node->lastChild->setData($text);
}
}
}
}
}
sub replace_tm_dtm {
my ($root) = @_;
my $doc = $root->ownerDocument;
my @elements = $root->getElementsByTagName('tm');
push(@elements, $root->getElementsByTagName('dtm'));
foreach my $element (@elements) {
my $first = $element->firstChild;
if (defined $first && $first->nodeType == XML_TEXT_NODE) {
my $text = $first->nodeValue;
if ($element->nodeName eq 'tm') {
$first->setData('$'.$text.'$');
} else {
$first->setData('$$'.$text.'$$');
}
}
$element->setNodeName('m');
}
}
######## utilities ########
##
# Trims a string (really, this should be built-in in Perl, this is ridiculous, ugly and slow)
# @param {string} s - the string to trim
# @returns the trimmed string
##
sub trim {
my ($s) = @_;
$s =~ s/^\s+//;
$s =~ s/\s+$//;
return($s);
}
##
# Tests if a string is in an array (using eq) (to avoid Smartmatch warnings with $value ~~ @array)
# @param {Array<string>} array - reference to the array of strings
# @param {string} value - the string to look for
# @returns 1 if found, 0 otherwise
##
sub string_in_array {
my ($array, $value) = @_;
foreach my $v (@{$array}) {
if ($v eq $value) {
return 1;
}
}
return 0;
}
##
# Tests if an object is in an array (using ==)
# @param {Array<Object>} array - reference to the array of references
# @param {Object} ref - the reference to look for
# @returns 1 if found, 0 otherwise
##
sub reference_in_array {
my ($array, $ref) = @_;
foreach my $v (@{$array}) {
if ($v == $ref) {
return 1;
}
}
return 0;
}
##
# returns the index of a string in an array
# @param {Array<Object>} array - reference to the array of strings
# @param {string} s - the string to look for (using eq)
# @returns the index if found, -1 otherwise
##
sub index_of_string {
my ($array, $s) = @_;
for (my $i=0; $i<scalar(@{$array}); $i++) {
if ($array->[$i] eq $s) {
return $i;
}
}
return -1;
}
##
# returns the index of a reference in an array
# @param {Array<Object>} array - reference to the array of references
# @param {Object} ref - the reference to look for
# @returns the index if found, -1 otherwise
##
sub index_of_reference {
my ($array, $ref) = @_;
for (my $i=0; $i<scalar(@{$array}); $i++) {
if ($array->[$i] == $ref) {
return $i;
}
}
return -1;
}
##
# if found, removes a string from an array, otherwise do nothing
# @param {Array<string>} array - reference to the array of string
# @param {string} s - the string to look for (using eq)
##
sub remove_string_from_array {
my ($array, $s) = @_;
my $index = index_of_string($array, $s);
if ($index != -1) {
splice(@$array, $index, 1);
}
}
##
# if found, removes a reference from an array, otherwise do nothing
# @param {Array<Object>} array - reference to the array of references
# @param {Object} ref - the reference to look for
##
sub remove_reference_from_array {
my ($array, $ref) = @_;
my $index = index_of_reference($array, $ref);
if ($index != -1) {
splice(@$array, $index, 1);
}
}
##
# replaces a node by its children
# @param {Node} node - the DOM node
##
sub replace_by_children {
my ($node) = @_;
my $parent = $node->parentNode;
my $next;
my $previous;
for (my $child=$node->firstChild; defined $child; $child=$next) {
$next = $child->nextSibling;
if ((!defined $previous || !defined $next) &&
$child->nodeType == XML_TEXT_NODE && $child->nodeValue =~ /^\s*$/) {
next; # do not keep first and last whitespace nodes
} else {
if (!defined $previous && $child->nodeType == XML_TEXT_NODE) {
# remove whitespace at the beginning
my $value = $child->nodeValue;
$value =~ s/^\s+//;
$child->setData($value);
}
if (!defined $next && $child->nodeType == XML_TEXT_NODE) {
# and at the end
my $value = $child->nodeValue;
$value =~ s/\s+$//;
$child->setData($value);
}
}
$node->removeChild($child);
$parent->insertBefore($child, $node);
$previous = $child;
}
$parent->removeChild($node);
}
##
# returns the trimmed attribute value if the attribute exists and is not blank, undef otherwise
# @param {Node} node - the DOM node
# @param {string} attribute_name - the attribute name
##
sub get_non_empty_attribute {
my ($node, $attribute_name) = @_;
my $value = $node->getAttribute($attribute_name);
if (defined $value && $value !~ /^\s*$/) {
$value = trim($value);
return($value);
}
return(undef);
}
##
# Returns a CSS property value from the style attribute of the element, or undef if not defined
# @param {Element} el - the DOM element
# @param {string} property_name - the CSS property name
##
sub get_css_property {
my ($el, $property_name) = @_;
my $style = $el->getAttribute('style');
if (defined $style) {
$style =~ s/^\s*;\s*//;
$style =~ s/\s*;\s*$//;
} else {
$style = '';
}
my @pairs = split(';', $style);
foreach my $pair (@pairs) {
my @name_value = split(':', $pair);
if (scalar(@name_value) != 2) {
next;
}
my $name = trim($name_value[0]);
my $value = trim($name_value[1]);
if (lc($name) eq $property_name) {
return($value); # return the first one found
}
}
return(undef);
}
##
# Returns the reference to a hash CSS property name => value from the style attribute of the element.
# Returns an empty list if the style attribute is not defined,
# @param {Element} el - the DOM element
# @return {Hash<string, string>} reference to the hash property name => property value
##
sub get_css_properties {
my ($el) = @_;
my $style = $el->getAttribute('style');
if (defined $style) {
$style =~ s/^\s*;\s*//;
$style =~ s/\s*;\s*$//;
} else {
$style = '';
}
my @pairs = split(';', $style);
tie (my %hash, 'Tie::IxHash', ());
foreach my $pair (@pairs) {
my @name_value = split(':', $pair);
if (scalar(@name_value) != 2) {
next;
}
my $name = trim($name_value[0]);
my $value = trim($name_value[1]);
if (defined $hash{$name}) {
# duplicate property in the style attribute: keep only the last one
delete $hash{$name};
}
$hash{$name} = $value;
}
return(\%hash);
}
##
# Sets a CSS property in the style attribute of an element
# @param {Element} el - the DOM element
# @param {string} property_name - the CSS property name
# @param {string} property_value - the CSS property value
##
sub set_css_property {
my ($el, $property_name, $property_value) = @_;
my $hash_ref = { $property_name => $property_value };
set_css_properties($el, $hash_ref);
}
##
# Sets several CSS properties in the style attribute of an element
# @param {Element} el - the DOM element
# @param {Hash<string, string>} properties - reference to the hash property name => property value
##
sub set_css_properties {
my ($el, $properties) = @_;
my $hash = get_css_properties($el);
foreach my $property_name (keys %$properties) {
my $property_value = $properties->{$property_name};
if (defined $hash->{$property_name}) {
delete $hash->{$property_name}; # to add the new one at the end
}
$hash->{$property_name} = $property_value;
}
my $style = '';
foreach my $key (keys %$hash) {
$style .= $key.':'.$hash->{$key}.'; ';
}
$style =~ s/; $//;
$el->setAttribute('style', $style);
}
1;
__END__
Index: modules/damieng/clean_xml/pre_xml.pm
+++ modules/damieng/clean_xml/pre_xml.pm
#!/usr/bin/perl
package pre_xml;
use strict;
use utf8;
use Encode;
use Encode::Byte;
use Encode::Guess;
# list of elements inside which < and > might not be turned into entities
# unfortunately, answer can sometimes contain the elements vector and value...
my @cdata_elements = ('answer', 'm', 'display', 'parse'); # not script because the HTML parser will handle it
# Reads a LON-CAPA 2 file, guesses the encoding, fixes characters in cdata_elements, fixes HTML entities,
# and returns the converted text.
sub pre_xml {
my ($filepath) = @_;
my $lines = guess_encoding_and_read($filepath);
remove_control_characters($lines);
fix_cdata_elements($lines);
fix_html_entities($lines);
fix_missing_quotes($lines);
fix_empty_li($lines);
remove_doctype($lines);
add_root($lines, $filepath);
return(\join('', @$lines));
}
##
# Tries to guess the character encoding, and returns the lines as decoded text.
# Requires Encode::Byte.
##
sub guess_encoding_and_read {
my ($fn) = @_;
no warnings "utf8";
local $/ = undef;
open(my $fh, "<", $fn) or die "cannot read $fn: $!";
binmode $fh;
my $data = <$fh>; # we need to read the whole file to test if font is a block or inline element
close $fh;
if (index($data, '<') == -1) {
die "This file has no markup !";
}
# try to get a charset from a meta at the beginning of the file
my $beginning = substr($data, 0, 1024); # to avoid a full match; hopefully we won't cut the charset in half
if ($beginning =~ /<meta[^>]*charset\s?=\s?([^\n>"';]*)/i) {
my $meta_charset = $1;
if ($meta_charset ne '') {
if ($meta_charset =~ /iso-?8859-?1/i) {
# usually a lie
$meta_charset = 'cp1252';
}
# now try to decode using that encoding
my $decoder = guess_encoding($data, ($meta_charset));
if (ref($decoder)) {
my $decoded = $decoder->decode($data);
my @lines = split(/^/m, $decoded);
return \@lines;
} else {
print "Warning: decoding did not work with the charset defined by the meta ($meta_charset)\n";
}
}
}
my $decoded;
if (length($data) > 0) {
# NOTE: this list is too ambigous, Encode::Guess refuses to even try a guess
#Encode::Guess->set_suspects(qw/ascii UTF-8 iso-8859-1 MacRoman cp1252/);
# by default Encode::Guess uses ascii, utf8 and UTF-16/32 with BOM
my $decoder = Encode::Guess->guess($data);
if (ref($decoder)) {
$decoded = $decoder->decode($data);
# NOTE: this seems to accept binary files sometimes (conversion will fail later because it is not really UTF-8)
} else {
print "Warning: encoding is not UTF-8 for $fn";
# let's try iso-2022-jp first
$decoder = Encode::Guess->guess($data, 'iso-2022-jp');
if (ref($decoder)) {
$decoded = $decoder->decode($data);
print "; using iso-2022-jp\n";
} else {
# NOTE: cp1252 is identical to iso-8859-1 but with additionnal characters in range 128-159
# instead of control codes. We can assume that these control codes are not used, so there
# is no need to test for iso-8859-1.
# The main problem here is to distinguish between cp1252 and MacRoman.
# see http://www.alanwood.net/demos/charsetdiffs.html#f
my $decoded_windows = decode('cp1252', $data);
my $decoded_mac = decode('MacRoman', $data);
# try to use frequent non-ASCII characters to distinguish the encodings (languages: mostly German, Spanish, Portuguese)
# Ã has been removed because it conflicts with â and â is more frequent
# ± has been removed because it is, suprisingly, the same code in both encodings !
my $score_windows = $decoded_windows =~ tr/Ãáà äâãçéèêëñóöôõúüÃÃÃÃÿ¡ââââ °½âââ¦Â§//;
my $score_mac = $decoded_mac =~ tr/Ãáà äâãçéèêëñóöôõúüÃÃÃÃÿ¡ââââ °½âââ¦Â§//;
# check newlines too (\r on MacOS < X, \r\n on Windows)
my $ind_cr = index($data, "\r");
if ($ind_cr != -1) {
if (substr($data, $ind_cr + 1, 1) eq "\n") {
$score_windows++;
} else {
$score_mac++;
}
}
if ($score_windows >= $score_mac) {
$decoded = $decoded_windows;
print "; guess=cp1252 ($score_windows cp1252 >= $score_mac MacRoman)\n";
} else {
print "; guess=MacRoman ($score_mac MacRoman > $score_windows cp1252)\n";
$decoded = $decoded_mac;
}
}
}
} else {
$decoded = '';
}
my @lines = split(/^/m, $decoded);
return \@lines;
}
##
# Removes some control characters
# @param {Array<string>} lines
##
sub remove_control_characters {
my ($lines) = @_;
foreach my $line (@{$lines}) {
$line =~ s/[\x00-\x07\x0B\x0C\x0E-\x1F]//g;
$line =~ s/&#[0-7];//g;
$line =~ s/[4-9];//g;
$line =~ s/[0-9];//g;
}
}
##
# Replaces < and > characters by < and > in cdata elements (listed in @cdata_elements).
# EXCEPT for answer when it's inside numericalresponse or formularesponse.
# @param {Array<string>} lines
##
sub fix_cdata_elements {
my ($lines) = @_;
my $i = 0;
my $j = 0;
my $tag = '';
my $type;
my $in_numericalresponse = 0;
my $in_formularesponse = 0;
my $in_script = 0;
($tag, $type, $i, $j) = next_tag($lines, $i, $j);
while ($tag ne '') {
if ($tag eq 'numericalresponse') {
if ($type eq 'start') {
$in_numericalresponse = 1;
} else {
$in_numericalresponse = 0;
}
} elsif ($tag eq 'formularesponse') {
if ($type eq 'start') {
$in_formularesponse = 1;
} else {
$in_formularesponse = 0;
}
} elsif ($tag eq 'script') {
if ($type eq 'start') {
$in_script = 1;
} else {
$in_script = 0;
}
}
if ($type eq 'start' && in_array_ignore_case(\@cdata_elements, $tag) && !$in_script &&
($tag ne 'answer' || (!$in_numericalresponse && !$in_formularesponse))) {
my $cde = $tag;
my $line = $lines->[$i];
$j = index($line, '>', $j+1) + 1;
my $stop = 0;
while (!$stop && $i < scalar(@{$lines})) {
my $indinf = index($line, '<', $j);
if ($indinf != -1 && index($line, '<![CDATA[', $indinf) == $indinf) {
$i++;
$line = $lines->[$i];
$j = 0;
last;
}
my $indsup = index($line, '>', $j);
if ($indinf != -1 && $indsup != -1 && $indinf < $indsup) {
my $test = substr($line, $indinf + 1, $indsup - ($indinf + 1));
$test =~ s/^\s+|\s+$//g ;
if ($test eq '/'.$cde) {
$stop = 1;
$j = $indsup;
# this is commented because of markup like <display>&web(' ','','<p>')</display>
#} elsif ($test =~ /^[a-zA-Z\/]$/) {
# $j = $indsup + 1;
} else {
$line = substr($line, 0, $indinf).'<'.substr($line, $indinf+1);
$lines->[$i] = $line;
}
} elsif ($indinf != -1 && $indsup == -1) {
$line = substr($line, 0, $indinf).'<'.substr($line, $indinf+1);
$lines->[$i] = $line;
} elsif ($indsup != -1 && ($indinf == -1 || $indsup < $indinf)) {
$line = substr($line, 0, $indsup).'>'.substr($line, $indsup+1);
$lines->[$i] = $line;
} else {
$i++;
$line = $lines->[$i];
$j = 0;
}
}
}
$j++;
($tag, $type, $i, $j) = next_tag($lines, $i, $j);
}
}
##
# Replaces HTML entities (they are not XML unless a DTD is used, which is no longer recommanded for XHTML).
# @param {Array<string>} lines
##
sub fix_html_entities {
my ($lines) = @_;
foreach my $line (@{$lines}) {
# html_to_xml is converting named entities before 255 (see HTML parser dtext)
# Assuming Windows encoding (Unicode entities are not before 160 and are the same between 160 and 255):
$line =~ s/|€/â¬/g;
$line =~ s/|‚/â/g;
$line =~ s/|„/â/g;
$line =~ s/
|…/â¦/g;
$line =~ s/|†/â /g;
$line =~ s/|‡/â¡/g;
$line =~ s/|ˆ/Ë/g;
$line =~ s/|‰/â°/g;
$line =~ s/|‹/â¹/g;
$line =~ s/|‘/â/g;
$line =~ s/|’/â/g;
$line =~ s/|“/â/g;
$line =~ s/|”/â/g;
$line =~ s/|•/â¢/g;
$line =~ s/|–/â/g;
$line =~ s/|—/â/g;
$line =~ s/|˜/Ë/g;
$line =~ s/|™/â¢/g;
$line =~ s/|›/âº/g;
$line =~ s/|œ/Å/g;
}
}
# Tries to fix things like <font color="#990000" face="Verdana,>
# without breaking <a b="c>d">
# This is only fixing tags when there is a single tag in a line (it is impossible to fix in the general case).
# Also transforms <a b="c> <d e=" into <a b="c"><d e=" ,
# and (no markup before)<a b="c> (no quote after) into <a b="c"> .
sub fix_missing_quotes {
my ($lines) = @_;
foreach my $line (@{$lines}) {
my $n_inf = $line =~ tr/<//;
my $n_sup = $line =~ tr/>//;
if ($n_inf == 1 && $n_sup == 1) {
my $ind_inf = index($line, '<');
my $ind_sup = index($line, '>');
if ($ind_inf != -1 && $ind_sup != -1 && $ind_inf < $ind_sup) {
my $n_quotes = substr($line, $ind_inf, $ind_sup) =~ tr/"//;
if ($n_quotes % 2 != 0) {
# add a quote before > when there is an odd number of quotes inside <>
$line =~ s/>/">/;
}
}
}
$line =~ s/(<[a-zA-Z]+ [a-zA-Z]+="[^"<>\s]+)(>\s*<[a-zA-Z]+ [a-zA-Z]+=")/$1"$2/;
$line =~ s/^([^"<>]*<[a-zA-Z]+ [a-zA-Z]+="[^"<>\s]+)(>[^"]*)$/$1"$2/;
}
}
# Replaces <li/> by <li> (the end tag will be added in html_to_xml
sub fix_empty_li {
my ($lines) = @_;
foreach my $line (@{$lines}) {
$line =~ s/<li\s?\/>/<li>/;
}
}
# remove doctypes, without assuming they are at the beginning
sub remove_doctype {
my ($lines) = @_;
foreach my $line (@{$lines}) {
$line =~ s/<!DOCTYPE[^>]*>//;
}
}
# Adds a problem, library or html root element, enclosing things outside of the problem element.
# (any extra root element will be removed in post_xml, but this ensures one is added as root if missing).
sub add_root {
my ($lines, $filepath) = @_;
my $root_name;
if ($filepath =~ /\.library$/i) {
$root_name = 'library';
} elsif ($filepath =~ /\.html?$/i) {
$root_name = 'html';
} else {
$root_name = 'problem';
}
if ($root_name eq 'library') {
foreach my $line (@{$lines}) {
if ($line =~ /^\s*<[a-z]/) {
last;
}
if ($line !~ /^\s*$/) {
die "this library does not start with a tag, it might be a scriptlib";
}
}
}
my $line1 = $lines->[0];
$line1 =~ s/<\?.*\?>//; # remove any PI, it would cause problems later anyway
$line1 = "<$root_name>".$line1;
$lines->[0] = $line1;
$lines->[scalar(@$lines)-1] = $lines->[scalar(@$lines)-1]."</$root_name>";
}
##
# Returns information about the next tag, starting at line number and char number.
# Assumes the markup is well-formed and there is no CDATA,
# which is not always true (like inside script), so results might be wrong sometimes.
# It is however useful to avoid unnecessary changes in the document (using a parser to
# do read/write for the whole document would mess up non well-formed documents).
# @param {Array<string>} lines
# @param {int} line_number - line number to start at
# @param {int} char_number - char number to start at on the line
# @returns (tag, type, line_number, char_number)
##
sub next_tag {
my ($lines, $i, $j ) = @_;
my $i2 = $i;
my $j2 = $j;
while ($i2 < scalar(@{$lines})) {
my $line = $lines->[$i2];
$j2 = index($line, '<', $j2);
#TODO: handle comments
while ($j2 != -1) {
my $ind_slash = index($line, '/', $j2);
my $ind_sup = index($line, '>', $j2);
my $ind_space = index($line, ' ', $j2);
my $type;
my $tag;
if ($ind_slash == $j2 + 1 && $ind_sup != -1) {
$type = 'end';
$tag = substr($line, $j2 + 2, $ind_sup - ($j2 + 2));
} elsif ($ind_slash != -1 && $ind_sup != -1 && $ind_slash == $ind_sup - 1) {
$type = 'empty';
if ($ind_space != -1 && $ind_space < $ind_sup) {
$tag = substr($line, $j2 + 1, $ind_space - ($j2 + 1));
} else {
$tag = substr($line, $j2 + 1, $ind_slash - ($j2 + 1));
}
} elsif ($ind_sup != -1) {
$type = 'start';
if ($ind_space != -1 && $ind_space < $ind_sup) {
$tag = substr($line, $j2 + 1, $ind_space - ($j2 + 1));
} else {
$tag = substr($line, $j2 + 1, $ind_sup - ($j2 + 1));
}
} else {
$tag = ''
}
if ($tag ne '') {
return ($tag, $type, $i2, $j2);
}
$j2 = index($line, '<', $j2 + 1);
}
$i2++;
$j2 = 0;
}
return ('', '', 0, 0);
}
##
# Tests if a string is in an array, ignoring case
##
sub in_array_ignore_case {
my ($array, $value) = @_;
my $lcvalue = lc($value);
foreach my $v (@{$array}) {
if (lc($v) eq $lcvalue) {
return 1;
}
}
return 0;
}
1;
__END__
Index: modules/damieng/clean_xml/validate_xml.pl
+++ modules/damieng/clean_xml/validate_xml.pl
#!/usr/bin/perl
# Validates a file or directory against loncapa.xsd with libxml2
use strict;
use utf8;
use warnings;
use File::Basename;
use Try::Tiny;
use XML::LibXML;
binmode(STDOUT, ':encoding(UTF-8)');
binmode(STDERR, ':encoding(UTF-8)');
if (scalar(@ARGV) != 1) {
print STDERR "Usage: perl validate_xml.pl file|directory\n";
exit(1);
}
# find the command-line argument encoding
use I18N::Langinfo qw(langinfo CODESET);
my $codeset = langinfo(CODESET);
use Encode qw(decode);
@ARGV = map { decode $codeset, $_ } @ARGV;
my $pathname = "$ARGV[0]";
my $script_dir = dirname(__FILE__);
my $xmlschema = XML::LibXML::Schema->new(location => $script_dir.'/loncapa.xsd');
if (-d "$pathname") {
validate_dir($pathname);
} elsif (-f $pathname) {
validate_file($pathname);
}
# Validates a directory recursively, selecting only .(problem|exam|survey|html|library).xml files.
sub validate_dir {
my ($dirpath) = @_;
opendir (my $dh, $dirpath) or die $!;
while (my $entry = readdir($dh)) {
next if ($entry =~ m/^\./); # ignore entries starting with a period
my $pathname = $dirpath.'/'.$entry;
if (-d $pathname) {
validate_dir($pathname);
} elsif (-f $pathname) {
if ($pathname =~ /\.(problem|exam|survey|html?|library)\.xml$/) {
validate_file($pathname);
}
}
}
closedir($dh);
}
# Validates a file against loncapa.xsd with libxml2
sub validate_file {
my ($pathname) = @_;
my $doc = XML::LibXML->load_xml(location => $pathname);
try {
$xmlschema->validate($doc);
print "$pathname is valid\n";
} catch {
$_ =~ s/%20/ /g;
print "$_\n";
}
}
Index: modules/damieng/clean_xml/xml.xsd
+++ modules/damieng/clean_xml/xml.xsd
<?xml version='1.0'?>
<!-- <!DOCTYPE xs:schema PUBLIC "-//W3C//DTD XMLSCHEMA 200102//EN" "XMLSchema.dtd" > -->
<xs:schema targetNamespace="http://www.w3.org/XML/1998/namespace" xmlns:xs="http://www.w3.org/2001/XMLSchema" xml:lang="en">
<xs:annotation>
<xs:documentation>
See http://www.w3.org/XML/1998/namespace.html and
http://www.w3.org/TR/REC-xml for information about this namespace.
This schema document describes the XML namespace, in a form
suitable for import by other schema documents.
Note that local names in this namespace are intended to be defined
only by the World Wide Web Consortium or its subgroups. The
following names are currently defined in this namespace and should
not be used with conflicting semantics by any Working Group,
specification, or document instance:
base (as an attribute name): denotes an attribute whose value
provides a URI to be used as the base for interpreting any
relative URIs in the scope of the element on which it
appears; its value is inherited. This name is reserved
by virtue of its definition in the XML Base specification.
id (as an attribute name): denotes an attribute whose value
should be interpreted as if declared to be of type ID.
The xml:id specification is not yet a W3C Recommendation,
but this attribute is included here to facilitate experimentation
with the mechanisms it proposes. Note that it is _not_ included
in the specialAttrs attribute group.
lang (as an attribute name): denotes an attribute whose value
is a language code for the natural language of the content of
any element; its value is inherited. This name is reserved
by virtue of its definition in the XML specification.
space (as an attribute name): denotes an attribute whose
value is a keyword indicating what whitespace processing
discipline is intended for the content of the element; its
value is inherited. This name is reserved by virtue of its
definition in the XML specification.
Father (in any context at all): denotes Jon Bosak, the chair of
the original XML Working Group. This name is reserved by
the following decision of the W3C XML Plenary and
XML Coordination groups:
In appreciation for his vision, leadership and dedication
the W3C XML Plenary on this 10th day of February, 2000
reserves for Jon Bosak in perpetuity the XML name
xml:Father
</xs:documentation>
</xs:annotation>
<xs:annotation>
<xs:documentation>This schema defines attributes and an attribute group
suitable for use by
schemas wishing to allow xml:base, xml:lang or xml:space attributes
on elements they define.
To enable this, such a schema must import this schema
for the XML namespace, e.g. as follows:
<schema . . .>
. . .
<import namespace="http://www.w3.org/XML/1998/namespace"
schemaLocation="http://www.w3.org/2001/03/xml.xsd"/>
Subsequently, qualified reference to any of the attributes
or the group defined below will have the desired effect, e.g.
<type . . .>
. . .
<attributeGroup ref="xml:specialAttrs"/>
will define a type which will schema-validate an instance
element with any of those attributes</xs:documentation>
</xs:annotation>
<xs:annotation>
<xs:documentation>In keeping with the XML Schema WG's standard versioning
policy, this schema document will persist at
http://www.w3.org/2004/10/xml.xsd.
At the date of issue it can also be found at
http://www.w3.org/2001/xml.xsd.
The schema document at that URI may however change in the future,
in order to remain compatible with the latest version of XML Schema
itself, or with the XML namespace itself. In other words, if the XML
Schema or XML namespaces change, the version of this document at
http://www.w3.org/2001/xml.xsd will change
accordingly; the version at
http://www.w3.org/2004/10/xml.xsd will not change.
</xs:documentation>
</xs:annotation>
<xs:attribute name="lang" type="xs:language">
<xs:annotation>
<xs:documentation>Attempting to install the relevant ISO 2- and 3-letter
codes as the enumerated possible values is probably never
going to be a realistic possibility. See
RFC 3066 at http://www.ietf.org/rfc/rfc3066.txt and the IANA registry
at http://www.iana.org/assignments/lang-tag-apps.htm for
further information.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="space">
<xs:simpleType>
<xs:restriction base="xs:NCName">
<xs:enumeration value="default"/>
<xs:enumeration value="preserve"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="base" type="xs:anyURI">
<xs:annotation>
<xs:documentation>See http://www.w3.org/TR/xmlbase/ for
information about this attribute.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="id" type="xs:ID">
<xs:annotation>
<xs:documentation>See http://www.w3.org/TR/xml-id/ for
information about this attribute.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attributeGroup name="specialAttrs">
<xs:attribute ref="xml:base"/>
<xs:attribute ref="xml:lang"/>
<xs:attribute ref="xml:space"/>
</xs:attributeGroup>
</xs:schema>
Index: modules/damieng/graphical_editor/daxe/pubspec.lock
+++ modules/damieng/graphical_editor/daxe/pubspec.lock
# Generated by pub
# See http://pub.dartlang.org/doc/glossary.html#lockfile
packages:
analyzer:
description: analyzer
source: hosted
version: "0.22.2"
args:
description: args
source: hosted
version: "0.12.0+1"
browser:
description: browser
source: hosted
version: "0.10.0+2"
collection:
description: collection
source: hosted
version: "0.9.4"
crypto:
description: crypto
source: hosted
version: "0.9.0"
intl:
description: intl
source: hosted
version: "0.11.5"
logging:
description: logging
source: hosted
version: "0.9.2"
meta:
description: meta
source: hosted
version: "0.8.8"
path:
description: path
source: hosted
version: "1.3.0"
stack_trace:
description: stack_trace
source: hosted
version: "1.0.2"
watcher:
description: watcher
source: hosted
version: "0.9.3"
Index: modules/damieng/graphical_editor/daxe/pubspec.yaml
+++ modules/damieng/graphical_editor/daxe/pubspec.yaml
name: daxe
author: Damien Guillaume
description: Dart XML Editor
dependencies:
browser: any
crypto: any
intl: any
meta: any
Index: modules/damieng/graphical_editor/daxe/.settings/org.eclipse.core.resources.prefs
+++ modules/damieng/graphical_editor/daxe/.settings/org.eclipse.core.resources.prefs
eclipse.preferences.version=1
encoding//lib/LocalStrings_en.properties=UTF-8
encoding//lib/LocalStrings_fr.properties=UTF-8
Index: modules/damieng/graphical_editor/daxe/lib/LocalStrings_en.properties
+++ modules/damieng/graphical_editor/daxe/lib/LocalStrings_en.properties
# daxe
daxe.missing_config = The config parameter is missing in the URL.
# web page
page.new_document = New document
# left panel
left.insert = Insert
left.tree = tree
# dialog buttons
button.Cancel = Cancel
button.OK = OK
button.Close = Close
# attribute dialog
attribute.missing_required = A required attribute is missing.
# unkown element attribute dialog
attribute.add = Add an attribute
attribute.invalid_attribute_name = Invalid attribute name
# source window
source.select_all = Select all
# help dialog
help.parents = Parents
help.children = Children
help.attributes = Attributes
help.element_name = Element name:
# find/replace dialog
find.find_replace = Find/Replace
find.find = Find
find.replace = Replace
find.replace_by = Replace by
find.replace_all = Replace all
find.replace_find = Replace and find
find.next = Next
find.case_sensitive = Case sensitive
find.backwards = Backwards
# undo/redo
undo.undo = Undo
undo.redo = Redo
undo.paste = Paste
undo.insert = Insert
undo.remove = Remove
undo.insert_text = Insert text
undo.remove_text = Remove text
undo.insert_element = Insert element
undo.remove_element = Remove element
undo.attributes = Change attributes
# menus
menu.file = File
menu.save = Save
menu.source = XML Source
menu.validation = Validate
menu.edit = Edit
#menu.cut = Cut
#menu.copy = Copy
#menu.paste = Paste
menu.select_all = Select all
# toolbar
toolbar.remove_styles = Remove styles
toolbar.insert_link = Insert Link
toolbar.remove_link = Remove Link
toolbar.insert_anchor = Insert Anchor
toolbar.font = Font
toolbar.size = Size
toolbar.align_left = Align left
toolbar.align_right = Align right
toolbar.align_center = Center
toolbar.align_justify = Justify
toolbar.rise_list_level = Rise by one list level
toolbar.lower_list_level = Lower by one list level
# contextual menu
contextual.help_about_element = Help about
contextual.edit_attributes = Attributes of
contextual.select_element = Select
# save
save.success = The file has been saved successfully.
save.error = An error occurred while saving the file
# inserts
insert.text_not_allowed = Text is not allowed here.
insert.not_authorized_inside = is not authorized under
insert.not_authorized_here = is not authorized here.
# validation
validation.validation = Validate
validation.no_error = This document is valid !
validation.errors = Validation errors for the following elements :
# tables
table.Table = Table
table.Row = Row
table.Column = Column
table.Cell = Cell
table.header = Header
table.merge_right = Merge with the cell on the right
table.split_x = Split the cell horizontally
table.merge_bottom = Merge with the bottom cell
table.split_y = Split the cell vertically
table.split = Split
table.merge = Merge
# forms
form.text_edition = Text edition
# equations
equation.preview = Preview
# style
style.remove_styles = Retirer les styles
style.apply_style = Apply style
# dnhiddendiv
div.remove = Remove the div
Index: modules/damieng/graphical_editor/daxe/lib/LocalStrings_fr.properties
+++ modules/damieng/graphical_editor/daxe/lib/LocalStrings_fr.properties
# daxe
daxe.missing_config = Il manque le paramètre config dans l'URL.
# web page
page.new_document = Nouveau document
# left panel
left.insert = Insertion
left.tree = Arbre
# dialog buttons
button.Cancel = Annuler
button.OK = OK
button.Close = Fermer
# attribute dialog
attribute.missing_required = Il manque des attributs obligatoires.
# unkown element attribute dialog
attribute.add = Ajouter un attribut
attribute.invalid_attribute_name = Nom d'attribut invalide
# source window
source.select_all = Tout sélectionner
# help dialog
help.parents = Parents
help.children = Enfants
help.attributes = Attributs
help.element_name = Nom de l'élément:
# find/replace dialog
find.find_replace = Rechercher/Remplacer
find.find = Rechercher
find.replace = Remplacer
find.replace_by = Remplacer par
find.replace_all = Tout remplacer
find.replace_find = Remplacer et rechercher
find.next = Suivant
find.case_sensitive = Respecter la casse
find.backwards = En arrière
# undo/redo
undo.undo = Annuler
undo.redo = Rétablir
undo.paste = Coller
undo.insert = Insertion
undo.remove = Suppression
undo.insert_text = Insertion de texte
undo.remove_text = Suppression de texte
undo.insert_element = Insertion d'élément
undo.remove_element = Suppression d'élément
undo.attributes = Changement d'attributs
# menus
menu.file = Fichier
menu.save = Enregistrer
menu.source = Source XML
menu.validation = Validation
menu.edit = Edition
#menu.cut = Couper
#menu.copy = Copier
#menu.paste = Coller
menu.select_all = Tout sélectionner
# toolbar
toolbar.remove_styles = Retirer les styles
toolbar.insert_link = Insérer un lien
toolbar.remove_link = Retirer le lien
toolbar.insert_anchor = Insérer une ancre
toolbar.font = Police
toolbar.size = Taille
toolbar.align_left = Aligner à gauche
toolbar.align_right = Aligner à droite
toolbar.align_center = Centrer
toolbar.align_justify = Justifier
toolbar.rise_list_level = Monter d'un niveau de liste
toolbar.lower_list_level = Descendre d'un niveau de liste
# contextual menu
contextual.help_about_element = Aide sur
contextual.edit_attributes = Attributs de
contextual.select_element = Sélectionner
# save
save.success = Le fichier a bien été enregistré.
save.error = Une erreur s'est produite à l'enregistrement du fichier
# inserts
insert.text_not_allowed = Le texte n'est pas autorisé ici.
insert.not_authorized_inside = n'est pas autorisé sous
insert.not_authorized_here = n'est pas autorisé ici.
# validation
validation.validation = Validation
validation.no_error = Ce document est valide !
validation.errors = Erreurs de validation pour les éléments suivants :
# tables
table.Table = Table
table.Row = Ligne
table.Column = Colonne
table.Cell = Cellule
table.header = Entête
table.merge_right = Fusionner avec la cellule de droite
table.split_x = Diviser la cellule horizontalement
table.merge_bottom = Fusionner avec la cellule du bas
table.split_y = Diviser la cellule verticalement
table.split = Couper
table.merge = Fusionner
# forms
form.text_edition = Edition du texte
# equations
equation.preview = Aperçu
# style
style.remove_styles = Retirer les styles
style.apply_style = Appliquer le style
# dnhiddendiv
div.remove = Enlever la div
Index: modules/damieng/graphical_editor/daxe/lib/daxe.css
+++ modules/damieng/graphical_editor/daxe/lib/daxe.css
body {
line-height: 1.5;
color: black;
background-color: white;
overflow: hidden;
}
button {
padding: 2px;
}
#headers {
position: absolute;
top: 0em;
left: 0em;
right: 0em;
z-index: 5;
}
#left_panel {
position: absolute;
top: 4em;
bottom: 1.3em;
left: 0em;
width: 15em;
z-index: 1;
background-color: #F0F0F0;
}
#tab_buttons {
position: absolute;
top: 0em;
left: 0em;
right: 0em;
border-bottom: 1px solid #555;
background-color: #F5F5F5;
z-index: 3;
}
tab_button {
position: relative;
top: 1px;
display: inline-block;
vertical-align: bottom;
border: 1px solid #555;
padding-left: 4px;
padding-right: 4px;
padding-top: 2px;
padding-bottom: 2px;
background-color: #F5F5F5;
border-top-left-radius: 0.4em;
border-top-right-radius: 0.4em;
cursor: default;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
tab_button:hover {
background-color: #DDD;
}
tab_button:focus {
outline: none;
box-shadow: inset 0px 0px 1px 1px #50A0FF;
}
tab_button.selected {
border-bottom: 1px solid #F0F0F0;
background-color: #F0F0F0;
color: black;
}
#insert {
position: absolute;
top: 2em;
bottom: 0em;
left: 0em;
width: 15em;
overflow: auto;
text-align: center;
background-color: #F0F0F0;
}
#tree {
position: absolute;
top: 2em;
bottom: 0em;
left: 0em;
width: 15em;
overflow: auto;
background-color: #F0F0F0;
white-space: pre;
}
tree_div {
position: relative;
margin-left: 0.5em;
}
expand_button {
position: absolute;
left: -14px;
top: 0px;
display: inline-block;
padding-left: 1px;
padding-right: 1px;
margin-right: 2px;
text-align: center;
cursor: default;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
expand_button:hover {
background-color: #DDD;
}
tree_node_title {
cursor: default;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
tree_node_title:focus {
outline: none;
box-shadow: 0px 0px 1px 1px #50A0FF;
}
tree_node_title:hover {
background-color: #DDD;
}
#insert button.insertb {
width: 80%
}
div.menubar {
background-color: #FFFFFF;
color: #000;
border-bottom: 1px solid #000;
cursor: default;
z-index: 10;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
font-family: sans-serif;
}
div.menu_title {
position: relative;
display: inline-block;
padding-left: 0.5em;
padding-right: 0.5em;
font-size: 0.9rem;
}
menu_title .disabled {
color: #AAA;
}
menu_title:focus {
outline: none;
box-shadow: inset 0px 0px 1px 1px #50A0FF;
}
div.dropdown_menu {
position: absolute;
left: 0px;
top: 100%;
min-width: 5em;
white-space: nowrap;
z-index: 10;
font-family: sans-serif;
font-size: 0.9rem;
}
table.menu {
border-spacing: 0px;
color: #000;
background-color: #FFFFFF;
border: 1px solid #000;
box-shadow: 2px 2px 2px #AAA;
}
table.menu tr td {
position: relative;
padding-left: 5px;
padding-right: 5px;
cursor: default;
}
table.menu tr.checked td:nth-of-type(2)::after {
content: "â";
}
div.submenu {
position: absolute;
left: 100%;
top: 0px;
width: 20em;
z-index: 10;
}
disabled {
color: #A0A0A0;
}
toolbar {
cursor: default;
z-index: 5;
padding: 1px;
background: linear-gradient(#F5F5F5, #DADADA);
box-shadow: 2px 2px 2px #AAA;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
toolbar-box {
display: inline-block;
line-height: 20px;
border: 1px solid #AAA;
margin: 0.2em;
background: #FAFAFA;
border-radius: 5px;
overflow: hidden;
vertical-align: top;
}
toolbar-menu {
display: inline-block;
line-height: 20px;
border: 1px solid #AAA;
margin: 0.3em;
background: #FFFFFF;
vertical-align: top;
}
toolbar-button {
display: inline-block;
line-height: 18px;
padding: 3px;
}
button-disabled {
opacity: 0.3;
}
button-selected {
background-color: #CCC;
}
toolbar-button:hover {
background-color: #DDD;
}
toolbar-button:focus {
outline: none;
box-shadow: inset 0px 0px 1px 1px #50A0FF;
}
toolbar-button:focus img {
}
toolbar-button.button-disabled:hover {
background-color: transparent;
}
toolbar-button img {
vertical-align: middle;
}
div#doc1 {
position: absolute;
bottom: 1.3em;
left: 15em;
right: 0em;
top: 4em;
overflow: auto;
}
div#doc2 {
cursor: text;
padding-right: 3px;
}
div#path {
position: fixed;
left: 0px;
bottom: 0px;
width: 100%;
height: 1.5em;
z-index: 2;
border-top: 1px solid #999;
font-family: sans-serif;
font-size: 85%;
background-color: #F0F0F0;
color: #000000;
}
textarea#tacursor {
position: absolute;
width: 0.5em;
height: 1em;
border: medium none;
opacity: 0;
resize: none;
pointer-events: none;
text-indent: -1em;
}
span#caret {
position: absolute;
width: 1em;
height: 1em;
border-left: 2px solid #555;
pointer-events: none;
cursor: text;
z-index: 5;
}
span#caret.horizontal {
border-top: 2px solid #555;
border-left: none;
}
dn {
word-wrap: break-word;
white-space: pre-wrap;
}
div.indent {
margin-left: 1.5em;
}
/* Tag */
span.empty_tag, span.start_tag, span.end_tag, span.simple_type {
font-family: sans-serif;
font-weight: normal;
font-style: normal;
font-size: 85%;
text-align: left;
color: #000000;
background-color: #FFFFB0;
border: 1px solid #707070;
margin-left: 2px;
margin-right: 2px;
padding-top: 1px;
padding-bottom: 1px;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
box-shadow: 1px 1px 1px #A0A0A0;
cursor: default;
}
span.empty_tag {
padding-left: 2px;
padding-right: 2px;
}
span.start_tag {
border-top-right-radius: 0.6em;
border-bottom-right-radius: 0.6em;
padding-left: 2px;
padding-right: 0.5em;
}
span.end_tag {
border-top-left-radius: 0.6em;
border-bottom-left-radius: 0.6em;
padding-left: 0.5em;
padding-right: 2px;
}
selected span.empty_tag, .selected span.start_tag, .selected span.end_tag, span.selected.simple_type,
.selected span.simple_type {
background-color: #F05030;
color: #FFFFFF;
}
invalid.selected>span.empty_tag, .invalid.selected>span.start_tag, .invalid.selected>span.end_tag,
.selected .invalid>span.empty_tag, .selected .invalid>span.start_tag, .selected .invalid>span.end_tag {
background-color: #F07030;
}
selected {
background-color: #50A0FF;
color: #FFFFFF;
}
invalid>span.empty_tag, .invalid>span.start_tag, .invalid>span.end_tag {
background-color: #FFE0A0;
}
span.start_tag img, span.empty_tag img {
vertical-align: middle;
}
span.start_tag img:hover, span.empty_tag img:hover {
background-color: #F0F0B0;
}
selected span.empty_tag img:hover, .selected span.start_tag img:hover,
selected span.end_tag img:hover, span.selected.simple_type img:hover {
background-color: #F07050;
}
span.long {
display: inline-block;
width: 90%;
line-height: 1.2em;
}
span.attribute_name {
color: #000090;
}
span.attribute_value {
color: #005000;
}
/* dialogs */
div.dlg1 {
position: absolute;
left: 0px;
top: 0px;
width: 100%;
height: 100%;
z-index: 10;
background-color:rgba(127, 127, 127, 0.5);
}
div.dlg2 {
position: absolute;
left: 50%;
height: 100%;
}
div.dlg3 {
position: relative;
left: -50%;
top: 1em;
max-height: 90%;
min-width: 250px;
overflow: auto;
padding: 1em;
background-color: #FFFFFF;
border: solid 1px #202020;
box-shadow: 2px 2px 2px #A0A0A0;
}
div.dlgtitle {
text-align: center;
font-family: sans-serif;
font-size: 120%;
margin-bottom: 1em;
}
div.buttons {
text-align: right;
}
div.buttons button {
margin: 5px;
}
input.valid {
color: #006000;
}
input.invalid {
color: #F00000;
}
input:-webkit-autofill {
-webkit-box-shadow: 0 0 0px 1000px white inset;
-webkit-text-fill-color: #006000;
}
required {
color: #B00000;
}
optional {
color: #005000;
}
/* help */
button.help {
line-height: 1em;
font-size: 80%;
padding: 2px;
margin-right: 7px;
}
div.dlg3 table {
border-spacing: 5px;
}
span.help_element_name {
font-family: monospace;
}
div.help_regexp {
font-family: monospace;
margin-bottom: 1em;
word-break: break-all;
}
span.help_list_title {
position: relative;
margin-right: 2px;
border-top: 1px solid #505050;
border-left: 1px solid #505050;
border-right: 1px solid #505050;
padding-left: 4px;
padding-right: 4px;
padding-top: 2px;
padding-bottom: 2px;
background-color: #F0F0F0;
border-top-left-radius: 0.4em;
border-top-right-radius: 0.4em;
cursor: default;
}
span.help_list_title.selected_tab {
top: 3px;
z-index: 3;
}
div.help_list_div {
height: 15em;
overflow: auto;
border: 1px solid #505050;
background-color: #F0F0F0;
}
ul#help_list {
margin-top: 0px;
}
li.help_selectable:hover {
cursor: default;
color: #30A0F0;
}
/* find */
div.find {
position: absolute;
bottom: 1.5em;
left: 15em;
right: 0em;
height: 9em;
overflow: auto;
background-color: #FFFFFF;
color: #000000;
border: 1px solid #505050;
}
div.options label {
margin-right: 1em;
}
/* source */
div.source_window {
position: absolute;
z-index: 5;
left: 1em;
top: 2.5em;
right: 1em;
bottom: 1em;
overflow: auto;
background-color: #FFFFFF;
border: solid 1px #202020;
box-shadow: 2px 2px 2px #A0A0A0;
}
div.source_content {
position: absolute;
z-index: 10;
left: 1em;
top: 1em;
right: 1em;
bottom: 3em;
overflow: auto;
font-family: monospace;
word-wrap: break-word;
white-space: pre-wrap;
background-color: #FFFFFF;
border: solid 1px #A0A0A0;
}
div.source_bottom {
position: absolute;
z-index: 10;
left: 1em;
right: 1em;
bottom: 0em;
height: 2em;
text-align: center;
background-color: #FFFFFF;
}
span.source_element_name {
color: #A00000;
}
span.source_attribute_name {
color: #0000A0;
}
span.source_attribute_value {
color: #006400;
}
span.source_entity {
color: #006464;
}
span.source_comment {
color: #505050;
}
span.source_cdata {
color: #503000;
}
span.source_pi {
color: #640064;
}
span.source_doctype {
color: #646400;
}
/* selection */
selection {
background-color: #50A0FF;
color: #FFFFFF;
}
/* DNAnchor */
anchor {
text-decoration: underline;
color: #0000EE;
}
anchor img {
cursor: default;
}
/* DNHiddenP */
hiddenp {
position: relative;
min-height: 1.5em;
}
hiddenp::after {
position: absolute;
right: 1px;
bottom: 1px;
content: " ¶";
color: #CCC;
z-index: -1;
}
/* DNList */
ul.list {
list-style: none;
min-height: 1em;
margin-top: 0em;
margin-bottom: 0em;
margin-left: 0em;
padding-left: 1.5em;
}
img.bullet {
cursor: default;
}
/* DNWList */
ul.wlist>li.selected {
color: inherit;
}
ul.wlist>li.selected span {
color: #FFFFFF;
}
/* DNTable */
div.table>table {
width: 100%;
border-collapse: collapse;
border: 1px solid #000000;
border-spacing: 0px;
box-shadow: 2px 2px 2px #A0A0A0;
margin-bottom: 2px;
}
div.table>table>tr>td {
min-width: 2em;
border: 1px solid #000000;
word-break: break-all;
padding: 2px;
}
form.table_buttons {
padding: 1px;
background-color: #E0E0E0;
border-top: 1px solid #000000;
border-left: 1px solid #000000;
border-right: 1px solid #000000;
box-shadow: 2px 0px 2px #A0A0A0;
font-family: sans-serif;
font-weight: normal;
font-style: normal;
font-size: medium;
text-align: left;
color: black;
}
td.header {
font-weight: bold;
}
/* DNFile */
img.dn {
cursor: default;
}
img.dn.selected, .selected img.dn {
opacity: 0.7;
}
/* DNSpecial */
span.special {
font-family: STIXSubset-Regular;
}
table.special_dlg {
font-family: STIXSubset-Regular;
cursor: default;
}
/* DNForm */
td.shrink {
white-space: nowrap;
}
expand {
width: 99%;
}
span.form_title {
position: relative;
top: 3px;
border-top: 1px solid #505050;
border-left: 1px solid #505050;
border-right: 1px solid #505050;
padding-left: 4px;
padding-right: 4px;
padding-top: 2px;
padding-bottom: 2px;
background-color: #F0F0F0;
border-top-left-radius: 0.4em;
border-top-right-radius: 0.4em;
}
selected span.form_title {
background-color: #50A0FF;
}
div.form {
font-family: sans-serif;
font-weight: normal;
font-style: normal;
font-size: medium;
text-align: left;
color: black;
}
div.form table {
border: 1px solid #505050;
border-spacing: 0px;
box-shadow: 2px 2px 2px #A0A0A0;
margin-bottom: 2px;
background-color: #F5F5F5;
border-top-right-radius: 0.4em;
border-bottom-right-radius: 0.4em;
border-bottom-left-radius: 0.4em;
}
selected div.form table, div.form.selected table {
background-color: #50A0FF;
}
div.form td {
padding-left: 3px;
padding-right: 3px;
}
/* DNFormField */
form_field {
width: 100%;
min-height: 1em;
background-color: white;
border-top: 2px solid #A0A0A0;
border-left: 2px solid #A0A0A0;
border-right: 2px solid #F5F5F5;
border-bottom: 2px solid #F5F5F5;
}
select.invalid {
border: 1px solid #F00000;
}
/* DNSimpleType */
span.simple_type {
padding: 3px;
}
/* Fonts for symbols and equations */
@font-face {
font-family: 'STIXSubset-Regular';
src: url('fonts/STIXSubset-Regular.eot');
src: local('STIXSubset-Regular'), url('fonts/STIXSubset-Regular.ttf') format('truetype');
}
@font-face {
font-family: 'STIXSubset-Bold';
src: url('fonts/STIXSubset-Bold.eot');
src: local('STIXSubset-Bold'), url('fonts/STIXSubset-Bold.ttf') format('truetype');
}
@font-face {
font-family: 'STIXSubset-Italic';
src: url('fonts/STIXSubset-Italic.eot');
src: local('STIXSubset-Italic'), url('fonts/STIXSubset-Italic.ttf') format('truetype');
}
span.symbol {
font-family: STIXSubset-Regular, "Times New Roman", Times, serif;
}
Index: modules/damieng/graphical_editor/daxe/lib/daxe.dart
+++ modules/damieng/graphical_editor/daxe/lib/daxe.dart
/*
This file is part of Daxe.
Daxe is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Daxe is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Daxe. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Daxe - Dart XML Editor
* The goal of this project is to replace the Jaxe Java applet in WebJaxe, but Daxe
* could be used to edit XML documents online in any other application.
*
* The URL must have the file and config parameters with the path to the XML file and Jaxe config file.
* It can also have a save parameter with the path to a server script to save the document.
*/
library daxe;
import 'dart:async';
import 'dart:collection';
//import 'dart:convert';
import 'dart:html' as h;
//import 'package:meta/meta.dart';
//import 'package:js/js.dart' as js;
import 'src/xmldom/xmldom.dart' as x;
import 'src/strings.dart';
import 'src/interface_schema.dart';
import 'src/wxs/wxs.dart' show DaxeWXS, WXSException;
import 'src/simple_schema.dart';
import 'src/nodes/nodes.dart';
part 'src/attribute_dialog.dart';
part 'src/css_map.dart';
part 'src/cursor.dart';
part 'src/daxe_document.dart';
part 'src/daxe_exception.dart';
part 'src/config.dart';
part 'src/daxe_node.dart';
part 'src/daxe_attr.dart';
//part 'src/file_open_dialog.dart';
part 'src/find_dialog.dart';
part 'src/help_dialog.dart';
part 'src/insert_panel.dart';
part 'src/left_panel.dart';
part 'src/locale.dart';
part 'src/menu.dart';
part 'src/menubar.dart';
part 'src/menu_item.dart';
part 'src/node_factory.dart';
part 'src/position.dart';
part 'src/node_offset_position.dart';
part 'src/left_offset_position.dart';
part 'src/right_offset_position.dart';
part 'src/source_window.dart';
part 'src/tag.dart';
part 'src/toolbar.dart';
part 'src/toolbar_item.dart';
part 'src/toolbar_box.dart';
part 'src/toolbar_menu.dart';
part 'src/toolbar_button.dart';
part 'src/toolbar_style_info.dart';
part 'src/tree_item.dart';
part 'src/tree_panel.dart';
part 'src/undoable_edit.dart';
part 'src/unknown_element_dialog.dart';
part 'src/validation_dialog.dart';
part 'src/web_page.dart';
typedef void ActionFunction();
/// The current web page
WebPage page;
/// The current XML document
DaxeDocument doc;
Map<String,ActionFunction> customFunctions = new Map<String,ActionFunction>();
void main() {
NodeFactory.addCoreDisplayTypes();
Strings.load().then((bool b) {
doc = new DaxeDocument();
page = new WebPage();
// check parameters for a config and file to open
String file = null;
String config = null;
String saveURL = null;
h.Location location = h.window.location;
String search = location.search;
if (search.startsWith('?'))
search = search.substring(1);
List<String> parameters = search.split('&');
for (String param in parameters) {
List<String> lparam = param.split('=');
if (lparam.length != 2)
continue;
if (lparam[0] == 'config')
config = lparam[1];
else if (lparam[0] == 'file')
file = Uri.decodeComponent(lparam[1]);
else if (lparam[0] == 'save')
saveURL = lparam[1];
}
if (saveURL != null)
doc.saveURL = saveURL;
if (config != null && file != null)
page.openDocument(file, config);
else if (config != null)
page.newDocument(config);
else
h.window.alert(Strings.get('daxe.missing_config'));
});
}
/**
* Adds a custom display type. Two constructors are required to define the display type:
*
* * one to create a new node, with the element reference in the schema as a parameter;
* [Config] methods can be used via doc.cfg to obtain useful information with the reference.
* * another one to create a new Daxe node based on a DOM [x.Node];
* it takes the future [DaxeNode] parent as a 2nd parameter.
*/
void addDisplayType(String displayType, ConstructorFromRef cref, ConstructorFromNode cnode) {
NodeFactory.addDisplayType(displayType, cref, cnode);
}
/**
* Adds a custom function which can be called by name with a menu defined in the configuration file.
*/
void addCustomFunction(String functionName, ActionFunction fct) {
customFunctions[functionName] = fct;
}
Index: modules/damieng/graphical_editor/daxe/lib/fonts/STIXSubset-Bold.eot
+++ modules/damieng/graphical_editor/daxe/lib/fonts/STIXSubset-Bold.eot
+J
ó¼Û:YÞÚ±(ÚWë5©YiWc¨%%:´-&%òÛ¦C_}¯ÔåEs²L¾^¬._¦:hÚ5$E£x ùBßBÖßP&,Doí_ã03K-Ãd4]ÈcìÿL#êUyÞ`K׶¼±IU¥ÍòñMÚûp¿òâjåÕ ©@uãÚ';ê|Ç!yã.#dÖto(ôØXrÃèVÞ¡*³ïÀHU`JscbaÊTêùWJðV¸@Áf7,+dLEã£cvýp©¹&ëÌö*Q Gìf0>6ÆPѾéà¹*\¬²
í\BÈâêäÆ'= 0eVF&®näcOEÀä²Uc!2×TøØø~Ec`I<±Ýróè1
ÑI×'ëçâC
m`[6oxâÛ
.¨ÿ¥ø_pF?Qý¢p«ÜxFWH0#ز¡1çÌD9÷g4 ×' 13.pØÆDÀ,¶8Õ²ÂÈÎ5 I*¡\êëzs;Ë×qí\÷x£R.Ælh6³C;4IÖ¬áJ¢=¢=êTw?EN]Ý
0MuE©ÛQam;ÀQ´«u°rÚ-¡A)óñпؾÕ1ÁÂè=å7{
AtE5éÛ/ºÜÛI´rD&*ÿÿJÿp7½Ð¿âØ
`Md-=)Õ¢7Í~¢e$£üèõ2RrÈGHr¸ÖzûÏKsÒKðVÿ~ç[GÌRó³-]»²c¤êö«à£+{'t(}!@rWU}h3qWPAÉ^
TäsÍ}EÎÉæÛª1ô *t¤ájÇý kBàµrJ*$'BÌÈÔ¥è*ðu.ÛiU)6´*1í`1- y!åæ§êÆ#èÒDG½t%òoqÄi0lë±^]ªI±dI©m(Ï·â%¹#mÓ:'6Vhtq°]9樦ø#=_J CAr1`hÛTBít`)¬ßr]-åX3*¤¾0¢R"
º°´8'F¨3`ü7@»8
µ<»= ©Q@$dJÃÒX|'Äï,NÍ-à ³%j!PpD°ÓHa¡%ñ×ÖöKÉÇS!äÊr¨ôUt(ïDÚ¬M
6¼-?x¾:é@ð`©¹ø^¸%ëÛ6Úî
P HpÅÚÂ]µl®!ÎúÇ{R°õ³À&å`L@»l¢m8m¿BjÔò=º¡×ó. ÞÂTzôíªî*ª* ï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:urð°fpô?£4wøÁtÄàF76rÜÛ"
²ÙÔ2ÉâÛ
³^CåDÓeóèiøGüfÝX4¶±²faÃÌ 0Q=X£¶·Ä~!ÊÁ(4ªÏK0 t:ÇIòü$·²#uL²|XY®7ø}Í£ñd²Ep¦&
ª°ÓÏ(ÚÈ
³¤H×~§Ç>qvÁd§ü ÈõIjÍx,pM5¿/%ÆeÝ2ÇFÅWDÏOÿÖX~\G8RT¥<¯ã'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¨pmÛ¯»Ð`,áêqøuÒ0òìhRFcg'£C
8Þ¡<D¦l
ü-UëÑú ÇÐÓ-Öû8Ec0ê2Ü3=$Ò®_ߦVÀCEa%&=àè»ìºþîù;ûp6Øp¶øs8sì(¾íÏ2vdиÂ\õ$Æ6aßMìjHùa¼ìÎoù] Ϥ0ûEJÀò¼a*%TêAk¨?ëpeAYÊB¾=¤ì¯-ÐPjöé¡Õ¸ø
²«ANÅ2TùfdBáT^Î$KÚ4Cé+·:m*ê¨h*÷^zXÕG5aË~½º7ÔîABÉÉûñéË +£î ¾M~&x?«¹µÑsfØÒÂÐY»ç*i;ýϲÔÞMt¯sÍq|[Z
71A:Ü'ÌÂHzNèË=G(ôp8uã\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ÇÜÙèïdhZó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¢Ý49)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Øbp5
ðÐZÞH?Î NÙI°qO~nûm¡)Á²ÕÜ^ÂFTÕ4¯xkln^ÕÔõå à£ÂU¹âÒ¡ ìûF%,4ïm2KfaJ4âµÈ dÀ×AQ9NSÜ
Ås¸ÈcS
Ô"ÅéÔb¦@AfY(ÎR2¾ IRL
ö- ÆxCÁ÷£qç_½wéJþÿÈR¡±¤}?Ñj
â:¯¶o°k¥bö¹:8ÐKó¥@tÝÓÆÓu"5bâ
Aðô¨¼°%<ñ¹<»Ê¬>ú{óNpO¬æûÐZ»òÿELm{i½
ÿËQ 8É
NÌgB¢¼rkEGÜÖ!ÇwTZã«çʼðS Ù²ÑsX`S(@[½@Üa4;*ùHi`&¨4AMh5äÐ7vM¡È=\°ÒlÁÁl[;øÞ«)6%H$ÉövNɨ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ôøÔÊfM
Z>FlFJè
à&zàì"ýÓ
ÐO
OÈÑ=&ÏøY.Ìâà<EÍ°¯Ïã³Z$¢B¡ÿÖ*8²Ê9Rïy-4ßéä<axN-'¶ÉæÃaÓàVàjÉâ1áÔc~|*%Éü|l88Pc'w]1m@)ªZ|{+V_|.Æö45qßÖúñh1â![c
ØÀl%õ4¿@×k]Cäàîç
зµÛÒÄOkJ}hª+Ñ5Å(¿ÂCnô=þð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äýNOø©À屮 µÖʸ3k¸®DÊ`Þönîý,C
\&
jÐ0
¦¤09.¹4Þ±`¶rýÁþsÆSZÇxíÅ1¨20<W¿*Ú öQ04+c at 6úè5¡5ÍçqÿärO:ì DÒ {Á?ªÔ¶kP0Øu<Æ8zÇ:g趹ÿD©dÃ~d5ÅöÕj'ÒIåì4͵±]¦[|×n16l:¥94@mH§!JáàãÂÞåR0áU¹XV<¥á"z¾*¸cô±7)<.TßV¼÷_·U±-úæI\?t|½`
X]-¥ð¢)Ü_dN¬°ÍS)W/¼2£¿M@
äç)u`d¹f£sp5<ªBóSÐfż/À¥ÂêàÎh/à:® ø}Os:®ytEH{uMôèïLê_pDZgZ³ÓdÅ,ñß%èÄ$Ƶ;PûûPýÅôãH4!¾äõ<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»¢ 8cgbuõ°¢ºÛ\³qCPæ20ÕÓÉ£àî#\sÂ×;9È
2ak¶À*ï¶Q,³4±vî"_2 #hDtÍÌèR22#Fwáù{HV(¸, (ÔyêUØTK^È 6×T#a$÷¼¡.ü8º3U
I''ØPH߸hkÌ!HÑ@jA¹ódGóPgäâÔ£1ñ¬7PÀÛc²G<YS U[nj8,TRB<ZÞ²Âi["j!óHC(Ñ#\@C:ç7¦IäD{n0aÖxyYOÞ¼×kC×(dÂ
Þìv'óèVÏ?$!,à8&ôSXßb4ÄßÃKñ3) ¼N VHwt§:½Àp=tc99m\`ê¯RmúÐ2"À̶Ю9ÄðC#êW$
m{
t$)Iü¤1å$2o@<îHF,.1ÌK"ÒÞÞ.iIÎår6N*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(¨Ôtu«
É)¢î*xþÕ4¬£,(x"Å{5Û<çÐtrIÑ'
SØÎZXAPv)8\- ú X®LðR4,Ô¯sÐÑØ:MN¾ÁH|8 Ëp4"²<ùüÇh¼óÌz#ã,ÔB+^UJWöæ¹¾&3f
3§ä7"îHbKPéØ!ÿÙQûrúÃ}ÞÙätòI¬¹7H
ò'´I°Æìæ.BÅIW®)ç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Àͤ<ie(ÚÿQLMóÄÿ¥Ô¡CÒ!¼tæ2~é{Òá×I"¾¢Z¯á §ÿ]Kÿ4pØn1û³¡Áª_ÕCê¦Gç¿òäÀhO:üNÇÈk\í.òßh²'(hõYjÍòÿs{Ùj?t/ämØV±ìlÉÜAÇjW/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!4mÓ¥,(æ%À%<
Öðpá§Ò
Ѭ´ä$1Í)Ä&4WP¬
:5Ôª½8Ö G®^lOPlîÍi÷_ËWá,^ÇudNÈpfS¨ÍQÓY
*ÚY-:²Í´;6U1£c8³?bØR·jK¯±4©!iG¤ét(êtêNt_eÜu.çq0ÑTæ×ç»y%f{È ¦E;ÖðqZ"ML{2|-½ð
d>YÎv>èÔróHñ
Ø̦-J^+¼Q!xBñ%á§Ëüè5ÝákÄwîÜ#¶4GjdÂÍÙ¯.-\\Z¶°µ]aj²¢Ð¢Ðe $Ë_Ó-zHµá"×DK\-q@µÅÑCgT+Ðó@wO\¼á\#z+M$^"*%8DÈe&GFdO1#²Á<cB¤À'¯H vâëW³Î{»â̺,4Cà%=´£ÉTÐíM±
Îr¹Z
43'êLuhc7ÒéÀCÑ'ov[%ñSè@÷+ØôÝgäl)T*kjHA
(²Pyß´8#$,ILhÕh¼d^éhTC mPD
®N$Q|[|Æ£HTp"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×
¹Ø?hXº]îA%6Fcù\¿uJºöýßhq=½*üðy¡¹Íf>hm¸ª6¯Vk1ä%ÿÝ°(ß¹RJÿ3ÝÉ_¯0¨E·iV5Âñ׬wHø!Åä-pÏÂĿסywôs½ªØþ¡imþ¦ñ(q
ª^Ð0yË5® ¿×ú©__'¾GA·Vø7 at zò ¨Ý
a,@Ø5N¼³ '8åôçwêá&i
efVC¢)÷<ÃÉePu
´<¤¼RI'oÖÆÞ¢ÿ¦ìÆM¥§ØàyQ;ãÚ^à êW7ä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ÊÂqZ¦k£,ä«EVklçß{ºFGtƺàpHÍL]æ(1Å/ÖjuüoÑ/«¼î/SI,ÄÂkèZÀBLÕG
ÛUCÑm0³KÃ
+j¨Ë}§ÏX!9êFeâ¡b¶,ÛºVq-~±¬qÂÂQ_ÎñôÖÍ;<ºý«çåÒfå/:¬°àÈ»úÇà³B
e/GbOF´l½ØÀa oM 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!ákI¾i&ø´`ÝþÁ¦®ÁÇSM°kLöÀØÓJáhÎôÆ#
ã
Ú)8ÛÅoqõØáÚuK$xÃø%¦»HVÈi#
£».W%Ân
7ÂdfY/a¾igvkô8#bR!Î ndØNäðKÜáR;'ý} Jj÷jVÇì°n¼ªéç4ÌóI;è¹i3J#xÀá*Àó³>¹¯hÇÜÊÈÀÇe@¬ûßÉÔMùLH!>j¾üÆF÷ÿ NÂÏãD¯x[ñdfJ° Ð
¡$¶<³Úhb ×tK0B(k
4l£Î¢5a:¹áÉÅÖ¦âyöU·nϹTÂ\pf¢!GT$§QCð¡° wÆÜæ7ªbu1I2Â-²y;i òJ.Ýh»Ö=²Ü£#î?MÄZ½!á¼Ë9Ѻ¢È&vã²9Ò3
Ë`iÑãý/R5§{5wS7ü]Ù¢
«8ùSl]2Ññ¶~5t&9«åÓC> « ÁüÆZX¨´¨E
¦¡ªCa/RT>wM&gip*vnf0îkÀ»ò%6`PBÇâòzè¶ÁMÂR;p-ð¦
½/O´½<¦u@·%¶"Î@¼"ØJòØmzãZ
H¸ËÌpòòÄ:ñ8 f=øD ÜúÓo£²+6Èâ
=}ÉÿßaÌ9`½-Þ¢ªÒ¥C$¨ûOÉ´ÀïDºÝmÎEwÇQ!k2n).F*GÝxb*%H½êÌËÏKb¿¯
Ý [_½¾Çä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#¦XMFßP×ó4Øç_À©7m
H>¾êÆ¡@#Ýt}IödÚR¤í²êcÂ%õáX<³kD;ÛÌ¿80·¯F9õãÂbü&6ô,oÒ+{Ã<0²,ü*?üäÔ£Þ©ûaf)|¹P¦ÆÈ÷úi£QÞN¶cTËÊÜÎqx®¶ÛweÝ÷jwÓ«ÁCKáã×&û$`ûo$Añ¨IF?ù&5#&$É$k~©4Ú^s2.!ûìÄ¢¦ÓÒö¥ÛÇò/KTsÔE²WöXy`ø/Gçç9pKXè6ûÖ4KÀ8Ñb0R;LK}á9ÑÒ61ôп~³ÌIuØ¥=§©6ºÕÜ.oU5A[r^ØH5Ôÿïèrî/S¬ÛÉ-É$èéu1ÀPFÄMb9Á®ÅÄÔÐ]t1WUN$)ëïåÊ-yÄ[¢Æ(Äqe!o"±+²H6eî¿%)QhmÅe²5XCa\ÈÄäý#àÌ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
~
Ä`¶ö 8tÜ"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 $"~F8gp2:x*9:ÁIIW
ôF<:IF;;þÛC%8
˶
f}Þ&'-Dê#,Íþo6þÊv/gv«1
\O0^v
$&¨,e0 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þzehbW
þà`AT:[=E<Z1:#,83Y<G :[;SA`
0"
)6k49QIT+'fBf1=cþY7Wq8U"
6 >:K
&60
(*5Íþpm 2G65In;`K73PHo
0'0/&
+<³ø"bÍÉI\&
)$+*Fè
Õ{
#þ½2Gq
!IC?hBZ+V')08:XF¥hG*2##9%!Y^a]E!&#B´' G($+$)E&68W_$$.( fKAx (- "!7
5ÕwUC-!*BcJGa
)5Æi
Õ§
ªû
s provided by Coen Hoffman, Elsevier (retired)
i
!"#$%&'()*+,-./0123456789:uni0391uni0392uni0393uni0394uni0395uni0396uni0397uni0398uni0399uni039Auni039Buni039Cuni039Duni039Euni039Funi03A0uni03A1uni03A3uni03A4uni03A5uni03A6uni03A7uni03A8uni03A9uni03B1uni03B2uni03B3uni03B4uni03B5uni03B6uni03B7uni03B8uni03B9uni03BAuni03BBuni03BCuni03BDuni03BEuni03BFuni03C0uni03C1uni03C2uni03C3uni03C4uni03C5uni03C6uni03C7uni03C8uni03C9uni03D0uni03D1uni03D2uni03D5uni03D6uni03D8uni03D9uni03DA
ÿÒ
ÿÏ
ÿü
ÿõ
ÿè
ÿô
ÿË
ÿØ
ÿÄ
Index: modules/damieng/graphical_editor/daxe/lib/fonts/STIXSubset-Italic.eot
+++ modules/damieng/graphical_editor/daxe/lib/fonts/STIXSubset-Italic.eot
º
*ávVåhYxæjû·¸0(ÌW ¾}¼cíÌ $±°Së=!U^Z7L!T~éÆIù× þ°±T:z&=¥ÆÙOXÌI at MlGõ
H)"ÉH«ª§)¡yd<ºàÌô@Ó
ÜkY«"xD¥a7zÐÆÛ¡²%AC6Y&Hi+I3ºR6äw$Ç&'Á½eÉV'ÊãºÆ¨ Á¯Á|V´+K¡Dòr=WE@?PÀ98VãG¸ÚXÀ18²â×
*hEbídéǬq²5F+.¤V^QÒ£@Åî+ÑV^rö«/CY{W ü½ëeê3NQw=¼%åáê!RãѾ£Ò(!Á0¬6Ãm¬w T"¡ÅîcnRvUF
Ü-Ö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ÀVWvFdýlghíO*ñÏêöÕå£Ç°÷%jÔþ08ÔÒEÞ÷¢ÊÕ¿
Ï«AFdvKM¨A¢fir¯b!Ädd"c=ÇãÌ4#
Ï7P¡E¶Þþ1Sî.N
Ãæ^»8´¾uAN~\fzµRTBêð °63êq)E¦&-ñy¡oÓc{31ö÷·9¶±éäÕâ¸l;éåwØ5j¯üM«!7)
#,x¯§¾>¾YºqR¢öÙ¦#Þ^ÈóÇÙ
÷Ψ¢ÆlQb$òüüuÓËEÌg¦Ü7©Ú(¨c8%32§Û|?<e,7QqO¼g
Q´4"¡JQÑv³QL{vOqµÉVQUS&áÊ°J2 I>ò73
4ílKhFW_XHO+¡¯CQX®*Ær¹¤°f·í*{sX(Û¹,¥ãÓ,W}2rÓ7-zõ³ô<³ò'p03²JM1¢p}""Îvs7t¦A2fÍ
M%5äÇA3¡ÄôDSâÈ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¥VOraFd·,çðŧ(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ÈFN½É;q¨uWV. w°OÄ-ÀÆ&à9õ;Ü)»ÖUqa«Ý7È5ÏÜæëìXUpÔ»#ëë+núÓÖ×'÷´ã°ZqV\2±FqíÜéÅPm_Þl¬9%®ÊÿJëÉS¯Ï*ÌëëèEÕãñ'¢kw>¾|PA4Ò Dç
ã
ÔÝ(qÐsoj¾Ò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~±ëIH3G at VéhOñ»Ôæ;Qq³=·ë×ôÀØbôúÖ"Û6éÉxItß&ÙÌäXh6Æû`íϸVÕ2°À>ÚHÓðõBL®HPó¶k A.GÍh&ìêðî:å[Ï´º¢ii¹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]ÖÀÒVW¿
ì´2/-ïa¶¬¤ÄÑañàÙ|T!ð\£Vßöà"ýÁ&+jý`üVËÐö@2ö±÷¤9S D~¹_PAÓÓv7XöÏR$Í^ªæa%'Ñ-)¯²!x&~óäÙíMLµ+ÀÀ,v Êé»çâÌÝ\uy¢¬ëã³DÿÆÖQ®ñ±¶ ê㲸_»Ai)A±LìCtÕG¤ðJó7¨4>IQÛÏ«2ʨðGeG§ªòDÊ3U CÚ1d·òfääà8<¦Ö3tÈ90¹Wsâ¨GûÆýåýÆA¨ªµ:ѵlc=4<ÁCå[áÖÛ¨}[,/Â'>¥`¾Ù1oL«!nÚ\¨%¶V¥¥6½ù]¿Â?é$;RKÕTÑèaþ^Ä?¢c2ù^àz8#î·§;e+ü##~6Õó>I¯å ¤RÐé>ú¸äÍyC²},°zO
ÁJÖ+9|Æ3`ßX*W¶¥±±Y/ëÑݾbêr
Su¼ËæÁäTm¥EåÁÞ§në)7n"ËT=v1Nô
XlH¼§Àm¦ÅM©¥Ô¯J)ÑQgWÖL`B§æ?ë¯r¢|ÿ(%²RñNª_«¼Np®FBÓÇaTÈ%Q)×WÂã}
ÀÔbhL[½[ÿÌ"06vW¨[vy<ÑÈ}aÿÇi?ª¿¼ù²ÇO~qoÁKAoEhfMÐÚÕïÛ?hRHOú¬3ú¬¿MÏ\ ò6 ÞÆVõ·cêø!]À§?§öeÄäS7F²ÈÛÅÄÒøñS+e#R·ù®WÁôóÅr }_ï)ZÁWLÂ9*ó`#aa!j0º×ïòÀæMjÅFãÂ<äøt¾d2([ûö ÌéÚñw`»î]ÿHËhD£à|g1ÏùÛ$hUëª|ùÞB ¼ ]Éq$,f~7WÄL§U@ñ+>åY÷¾]%~wä ¥©HÖìØí;58!1bñÏFB~
\s¢äxa5F¡J6d). ò÷½'\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Ë ¥Ã*ß%£hEÂ/!Àã"¬. "Õ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ÆõBPP³¡p`)l1TÅ&LúÑáMþòÐà¡G64 ÞúªUúè7N1Íç/«GÙìʲßñ¦ÐhÇÆÕ_ÀÇ-Al;§Æ-»½Fg4NATÁï¹±Qdcîk$j`æ¯(ª§/J¥K >!VáÉÐÃ)»q:#cAxX"+.ÆeTiή
ꤡT§¯8Çà8
MÆ,¨ÍFʲá'Vø^²sòAbÛÃmuöªf®¯¡»
lª·¹Ìg2ÇìÑ®KÀFéÍTz5÷æjó¢xýuøhéeÓ,9ðÁ¦Vw²^¥@èDöD)`bw8ØëâR°È
¸ûÏáRÜCf ê§n
<Z.æqÁÀçjùêîw\ÃU8
âZ;=Â%.Ù®Ì)¨Ck1ÜðÃȹ$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ÊWlÁL÷´Ku0ýj8úÄÊû&ݳTiÎb?nDq®°æ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ù¾G5k
i¡Ðó ^v
òÒP«¼QGÔ¸9|TùzçÔà<ÒѸ t ué$áG Ô¡Läà5Bñµ®.+þÊÒ%ýãÞ*>Ä;À«dn().2$eûFý±]ðxin¾,µ(´.Z*¨ôR+
§cÄô¶~¸|D§
6z\"E=8£cÌå¹&<~ zê:kk_O³¸Eä@r5X²y®¹ðÈHKeÑ4XB|Nq®õUp%ý=XHgH1Áq=çvÛDá&f¬ï÷êÚÅÀäX3N0rªr$NÀn{«¦J[ÛH×â$,Y$eWT¬l#bÊÊÕëT<U³xhoØé|^ê`R«B¥ø᧿µ³NZ? ÛéÂä3·Ë¶ó&¯\Û~Ç´BæØ*ml×üö@jþTäaãæáQƼ.åJÖ>ßÆn
áÙÅáÓá¶Ü1âÄò0,qägUW¥íÁOüúË4p'Yd[È}u}
¡Ó-¨99ª +¾Ðqfa¤ßÁÿ9æ$ÛàþHJqr2ÆMâÂb´´bÌ<=C{S¹Ã®a)8N%uýPÀö¿w8àö'¬® çØì`Ù©¦\l@ë±þï01Ú_s@Ðõl57¦(ç u^!Áà K=O^qÇ9 d>¥âQH´Ã·Ö
ãþ¸F¤]Õ'Ayá:4ç*´£5=:0]]jïf^?T궴¨ý>A#À¤µ³ÆzÀë_8õ` à¢8Îe at OîØÁfo|Øoè3@Á}¦Ç¶JP¼ÆKjåé<ÓCö-t,
HwvûÎPM Ç»g&,M´:Mk]³ÞT
°¯EZ¨ÆìòcÖùM7MjBy»qq£ÂøhÝ ¸º\¼
sr¤r}Ö¥ÕP2%y§meÄE¥åÜ|¢Z;`\ü½¯âzøSÄðø³¼ºGz%Û0O/
w¦ÖÔ-vö郞°Ùn&&UoN L6_^/P-(¦qbÁÄpª8û\ø-d~rQA0Á-±¾l0ÆaiPä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ù°;ȲbIuÚÎeÈj¸éιF»àÈV·Z¢«®,ß £À?ù+ÿ±ÿò@?ªÙb~ÓÐ.ѧ;¥¬øÛ&>f´¦Äàÿ
⧳uËÔ6âz:þÊlÓ&[µÂhôZ>¨²®©iÅWKUX\Cu)³pH¦ô1Ã#¾²AÅdÝW®É:½ØX+k'åÔrÖ\Júú3yKé{Ñt¿©éü?TÄ£¾~&.-FDÒmOÏ÷â5^Ô´aÇ!¯»â? ´¡wª&Rq´Ú+ÿõ¶î³
=¯aQ«,véy/¶*ÂÓYÈï`Åoø`×°X/ÌQoòª[d at ZjæÊ:ìB¯·g«^$±OTô'¦×¹JVÃ6Õ VTMz\ÈR{޶ܹQÀM[<×Ì!MCqQá9
û˺¼¤¹BEèF}dZ¦UãõÐvÑ¡]úAÐ.Þñ Xib^
xüz{uÒÕ¿´å:ZÕñVÝ @+hýv3^
nðdµ¯Ý*¨X¡&0ùüIµêy6Àà¬5»<mI<r¡7÷¼ù0DVø´+çdÜÛ½Ê÷Ñs£`ì0=Æwù¢=pa l`Ô=ÕngV;q.;¥=̺¨rÉaóÃTÔB÷§
Nô`£pTh&C1x¡w"ÑüæPÖ,5à×f$Ó^·¢ÕÖÑoc`Xk¬õ×%+uÚj¨g(gP0sMüª-KµYpzÐhÚ¦«dp#´OýK°çÁúZऴ¹Ó÷Wç*zì0¡,5b~wfßð
r,QZ8¸ÓÆϱköö)|û&@XL8¹_pAÁá:ÜÀÞ¡¨9ÅÃÄл3ÓJ uд+Dã¦+uR¢ Ñ?¢Lq±$£jFÐV"FKtàâh+M8ì
ÙhȲ¤æ¾Àr!òë¢ifÆy+7¥`S²RJ9ðR¡8`$î÷"`®î\Ñü5-]ÇlF0£R+ÑOJ¤p´ ?XKÁùá:nõé¡f2/~)Ñ"è<>è'%³O$[0D§µ3ú<ñà!ÙéYk@';p(¡`®»ÆÛ÷Ój2-8~°£bÞhÒ#ÊoÒ@Á?WBÊùô'¦ÓÌÖ~ùBX~ iÖ]<4óSÁ'ÿÞð±}pvüsRqõÇõ^2sâ(hw©/ÔÍêÖò0̽m¡·6$=O\Ù~ÖªëC
ì ù&ñ!XõØ-)upR2yÈäk?ÖÙ '»5t1Hã`µJÝ1)ÿòPå\ UhHJ(þL"Á|aÛáðàU\Ã}åÏö±¤±sðìg¬Äãæ
[%8Ë.j/WµâI/9$lÓ_ÛÐ]
'ØAÚû¨ÝÎÅÇp¢¬V°k+lE8}¯¡»í¶¡âêØ:bΣx#2Ðm0òÓ5ST²8%S1k
P÷VN×G$
"cØÊ&Nhôr;e{â"åH;RPW!ÂFõ8¼\lßÚâl éðm©IKK(ü«è/%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^YVûÕâ/zS C
³aU:j{¯DQ"yÃê$?ãÚK?ccS#Íop(1)д#ÃI:âvGQüï'´Ç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áMt¢OµRõq-ÃÎÓu$!ÓÙü±!ü¹×öC3,Å|·0
PÛä«ÈÌÖ%þ6yH¸ú²©Tp,«u3q£(èûÓEÚXémb¼,Akp &¹6j0Á¥Â$Fª§ÇÒÆèHÍtUÒ¤Xña07Ê%â~·æëD[!~H4 .ØORDýC×ëÇvýEÁ{ÕÉíhOºa¼y£Üm®mjA¾Æ
¯üÈOÃå&N
?Ò<W;ËaÉ> Ç~//Çöü>Á4È¡¹¡¤j~¼ y¹p1ÞPÏR(§ÌçO³¹»#Å_ F#Îúj J.´È±=A'Y^¡qq1Øj(
ò1}nI*ÒØ,dYP+ª%ãØØ=k«¬ü 44àìug¤%¶ØÀ±ÅV¬jRk;Rmäx#¸}Î,R7³â+?Ñß(.ß¼
6FIekð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öùáJH ¼ðXï×é
mjLC¡Ç:¯¿ÉºÕvô ¤Ì
r 17Þ<»¹apkºA*?%²~D*àË° ©¨Þ!?¨Ø¿IHMKÍ`7WoáË
pߨêô¦`rf¹µkü¿]5(âhw<ØWù1,1ÎR×íü¥1ã(ÿdѵú_iG$÷¢4r^Â¥ 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êV8²{aÃÙ4Q¹¿LºH·Õ¡M7ÄCZ
Y´¾S/VÊZ±ðÑ©Æ´ód>nMCX-:ò¿ü¬I5r=¥§by}Ò+Bã¼V·p
R@'=8ºS
^z´¶º´¬kzb¹XÉ)Bðrê2á
§½j·,¸$lR¦Ôýk²nóh;=åK
©tw¦ëD׬I2
N®,Îé
ð| *A÷»¶+~ç»lu7KÙä3`)'É-جzë« tß,Ù¬Úî¦É(°åê4¢Ëâ!ø¨|Ï$øà ª÷æé¹fÎlØù:½7'Û×9'& µ,¡éÛ&GöBÑ pÚél=@MK¸^é°ªwIõõèQ)Ø(#y£%Äl¾ÿlUëdMt_)\Xu¨+ØmnÔGGõÑgÐÇNVãÖñ9 <À0Ó@2;¤X«V³\4¾x;|4« måvv¼³<yVVsTW×Ñ2ÄË·Æwé;KfÞ
¡ÍÛÅ9Rbà34_ÉzªJ£P£J.@s7ð*IzöÜáfæþ4CXáõ¥X
x)úØGòç£Èô8SÜ p´ÉëÖ#A?¶
¬Ä"Ã5¬ræ(ÿ5Ãv ¡i~̪a(»±D
QM߶nÞ¨oÊE?NËdÊþfDöÊè P¥®§Àævzdê{¤8TgýKa2| ÓSZ/'D·!ýð¸0$ãØuûÛÂ7äOó"¤ü°o at 8Äg¿°§$
,WÙ¥é°êäÜàVÔ;²%ÅÁAT¹|ãSl&'uè±¢È:OHõÌص®!t)ÄÖ@¢l¦¥øiãfba[¬äû5P½*Q«
`ÕxQ®·yÜÌáKÌ_×°ÜÏ:3¶2q±ñà6%j÷ÄfÃúák¼Â¿;×}n+«%YÐ0ýöÝÍ®îõéIrJÒúâóTN¤Û§çÏ ûØò¬Ô¹î!íÑ.Üb%{eáxɽ/`.ÜÅwJn´î5¹Àæ"3»Þ9{.0ñùÐ?3d§sÄàíüÂ.E§_þ7ô´1HPû"ªã::¢¡33ûêlåøõÂ-/@µBm~ENa²ü
öp9?I«¯êP»Ùñ+¸?ñ<QäWÒ묻Gl:#y"*N!£%¡¶g\q65_[PBc½F®\ñJùA
ÉÏ°ï
°Åû¡ wÛ´jIq¨¸^ÈÝQ¡þb&ænk¼;£kÊ¥_µò¶jÌ YE]?¥¶¢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¦EbÃÍ SãIÐV$?{x=ý]c'Ó@Ì·ûý"qopêV~`ÎO"7×%rÙ4íÍH×ÇhÑ!Øî?Äö§WHÊÖè8@cý §1ZaßIâ¥däÈMü
_³Ndõï«Dc¦Òl¢É7 at Kñ7<gÄ>ÿE'Ü=Ò`Í÷È\ºô_^Ê3eýøÉQ¡:¢*X
%S|±Np;?$B"$.v[Ìë8¿ßÑôüñid~ º òe5ØømHÆoA¹Xbh!K÷Õ¤: _K
ò$Ììs¯uT¦ªäïb;ÌÈØb½AG>uÿÈì¡,£MÀhÀâ£9ogÍ[íDàíÖ!>ä(Aª@²(JX;89d>Öué[Waâ>ÑÊJ&¢"ÜÖÍjN=ÕÉZª[
²¼W(t¯M2Z9óèÈ¢UIP0¢Hvl]#À©éFèÄÇÃÊpÕ)Xé{tª øæb,~Xµ
UFU¤cez(uЩðÌGÕ¨îiõÊÌBü×¥Âtt@qè0*µÈAA*
ÂÁ pìlÍÕX/æLB¼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Ë#;=NUØoqLÚkñu«âoðÔ40ijêãtee<±HOFaRçè×W`Cl2åÇÞ«
3KF$¨S7ì>5¥ÍÏ7òóåãË«m⵸ÙbÏ¿À¿Ë3E"Óî<=-_«tn6Ê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Ó§BTT`-ä:Â(FÙoO¶©ì6M`Ê6@ß)uCDбM·)&µ7¢KÔ´¤üF&ÔÆ=«ãN`0R&¥JÒ4Ýõ_ûEÿ BÚç¹\¬¦]º+Õ;FÚ^8ãA#¼.Bî¶dõöÂóæs:®
~5=Cä°¦hìG¡|Uað»8éç[¦!Örj¶ÛM at r8éll¿;Ìc>ÄS&±k¬êðS%bun»
Í)ìæ6"©$ãoÚÑÊnâ´EòØæÆn?2ÆnèóãÁ-%.1+ÍÄ·"ÍY'çÏCGc![W·MǪµLP#£èÆpyäÑW%lLƾÅÜݪl¢¦Qº<ª=åÕ%þíÈöv·$ñé8³0%1'Äüôäb%"¾$NÀKuÓ û.ÙÎÇ l,Ó3Ì×ådxº{cZã$Þ y$47};;C3Ú.{ÂÎ,¿¬¤ùâF1
X8L°,îùê
!ýæ§YAp¥`í©$~¶TëL#Îü 9þÁÞØH`üg/[r¾&ë
Å}yWv×âakXSÜ}Ãït
Dtå0è=nÀTùÁi$º/D#¤YÒ9p¦VĦ@áÝ[°ÜswóÄÃúɲ¸mÉÌ<ÿñd¢LìTnáOØæL3yøRïhÌCcwqNâ
æÄ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´DC*ÓkÝõÂhLÀÕõ¦krÊÕ{¬§åÁ1/XUC\#¸
º ãÙ®Rpzùw¯o){¼ÛÜCO7»£²æaëì$§n©±U
¿ppèZÍ+Ãô/æ HÅ5¥í1FÅS1f)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»²atbq[%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=Ì'½_üED
ê®Yq6b($'ÀèÆ
à«P#bðEáÇéÒ
:¤Ã¸ÈÒ}b<Z
A±l0ëÆ#Z<½ËNVMÁaäWSÑTȹ1LfÓáT2ÅCcI0éz${ÑÆ,P(ËTXwUþ\¥
SAqÄl8ÇjÆ¢À:JÐ.PRbNh àà×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åBAÿ;h5cÓ9©Êl¬VH²ÇCúv8toÁ9!]:ýaZÒWÖ¨¶ÒZz3H¢è8jQ¿JLBmØÇ
â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:©9iø{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ÞgC³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] Ì¡¾@LU9¸Ôksñ¹òÔa"è854êвÒËÍ& ñ[KànºÖ.±f§K9Ãr%Ò(Ìk9¶KrÍRø¸¡`Q.ØпUÀüäPäØ^4^Úàb$±¬>PS½Ã1§ÿáîÅ!%F¬xªï)X~ÁXÔ¹syÌ´
õÉÃ;d»øËddgCí4=´¿ î&tb7ãé·FÉgÖàJ0ÚȺáM(/B¶¬nå
§ÈEÄw²p5Y AÇì(ËÂÝ@qýÛÃP§·ÂÖ<Ü`ôýZiEko"è.XФD9º:óöpåtûQ(ç1o{ÃÛ®y{Û8NÜwÁÔ<¹¦©á;¿£!þK1ôDgJPI/Ñr> G Ì0~à µÔ̤ÒüJm«C0xì=,ë°é^y²_÷ètµ`Ûà¡Rk3 ºÓó»áþE7^«$@/n(¦.÷ü©Q0JÉý·KÈéµµÄP¾ø®Ú×;>}asÌÉhæi pqÇïÏ
HYùCÔ¡L±$·F'à1HÛ9vL0òµ¹
¥SG¨ôLr EFÂ?ÔÄ%lÇ)Ö>¦¢ :&&.<0\ïþdqÞèGpïqðVâ
ãýIÅÔÒ1
m¢%©]O
4Czlh°xÅ~^Q»ÄÞÝzô" ð~©é;&nj=L3*ëXsR½Ã¯ÒÌòÐ]tî ´:ÇÏs~ ±mZ(T/Ùª&ëæ2ñ©¹ ¦T ¯Þx)Æ-3×fGA¢W ä¯ãGé
Cá]ÌÀ§Ú*ÇËvuªDðÕzñ¨zÎ:ìª5m`ùàNRÚ½W¹²ÚùÀðÑ0uXzû3ej²b at 9kEÈèSë¦i ¥fui+ɶ¥Ýa©5
Kuµ;3F«áq3çp7¤kÖEl ìzçE¥.¬fÆ¥h%à=z 5/é0äøÉU;Ô
¿*J&ÞXÎôc¾¾
¡
{
5Ér~û!S 8ÜsB12b,2d4Ý& -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ñESh¹ºÊѪ ÝÀÞ;{sfÓK$ ¥®] ¸.#ÌeMà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óè
êBSmK8`ò*äÏÒc@%äþjLTPößtµ®ÑµË½ÀTøeÑÚíãôHU¢$ëQ°rnX!Lì
Ö ò³Ä;¥|²
è
~8³ÂSÈS¥SÂ~gS=í7ÑpûÄÌÙÝ`
ÊÇ×D¥õ°Èc
)v^<G¦¦
ëLmpú#¤%ºo\û`¦ÃR[W<|çàLJtÐw% 1C-ýÃ&ky º{`|ê¢%`F2VÛ§`ò"¼Þ±õ/[KõG$Êhc1r4°ì
"µóBUDyÄ2^²âòÒ|[(Y %óñ(R£g0M*ïØp` ö®[xaD£#üí`Oý'KÅÒ cÑ5Jf¢Â,jtJk/îÐ\±F>g*þ'Áø(jXNB}øÖä7õ[:v8Æ|±ëô=:Ê_wòéà,Ù
ó9Gw îѪ
ç~<ò©ZócÂm¿)·Ø·äm^WémÔ5Ü¿âÆtDõÔ!MÀÄÕpïp¦²åaR¢`P³§æ$½z-ÉzÒÚL%(ã6ÔiÎY¢ÁͪáèøÊ`4Vz
4î|EÄ%ÉÆ襣Ih4bV.#«mlÆXHa5Vq³^×ÙH b$ V7Énã¤ì¢Lz:+Ù%¾Ù&
ܽIÈ\ý¿·-Ò£xôf_ß{gÜÉw:DǦ`øîÂVxBVq?íbï¤Å½¸!1¸·Öe©gD«a sSF9.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Í{çÏ*Ö©êmrPFþðç'#(¦$~ê«ÌnÅ-¹c(û=#`ñ¯G07ûæÝçL×æÑüî
O½iôw(«»¯-À³4F`]¨qFþ#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íµ&stÒ°øz¼l½ÝrÙ°
4ü<(2ñÑÌYl
¡ÛÁ¿#Ú!Jr®ú9X²z|8Uº^#ãÏ %Aÿ}Ò}Ú3ô -Üø0ÐÈ
7¤/ûËðºò
|T<5Nû²'©¦»ï÷ôV,"dæj"üHC¸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ñoL;Öõ:O·9SÌ×6mt¢¿ÈËLΡSuìe½ÐL°»C @±£¹Ö6ÝIïÂH²T×]ÁQ,ÌcØ!÷n(gyB9%Á÷Qâ}X`QttÉÍùÃÉw9PDÇn:º·Ó{ºröxý1PFº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èÇyTû^ósá-aï÷Hf&wxãqôE2¥m$>ûVéI¥¤§Agtºpja6q®CÜuìÒ/oúÏFÁ@·nZó8u¼5ÝPH7\
E.¹î
hC4A
ìŬäD|8ª!íóÔmѦÑø³
Êûñy«ò1Þâtu7"%Á4 ¾Ð¬)~ºÚ:ØPK.¢F²&r
Üò~AúAI¢#]Å®Ly>ë®
:ýè'B+/IéìG!fÍ_þäcäcr<µ;ÖÀ3Ñ1U¦qu]Ã!©CS*°nzTz6]+%£;ôØQ¿_2«&c¯~à3gÂÛõ;Á,òt«Z©ÌêDFòw
åT>
è
ïzUíQp1> 2 ½:@¶Û»c.R
°;·+ÓÆ®~ãtÏ,0Óß
ZìÃE
æ°JXGûB§S·ÑéB·g*hwö4ËTU2Wñaé¡Ê¶tÙ?:BPEUüX(ãP
?`ÀÈuûá«¢ÐXÖ¦2º¶Û,£$$ FmÀ k xú=LàgÜøS¥H- at s05\¤µw¼RJÄ;Ú
û´,ö Ï,&¼¥ÙPËðM){%¡Úà0ë*Í×|;gÊEAêFb_¯ÉÓ´SÐP7ßFZpWÔ¢S.!ÖEQý°é°k(tÔênèð(¡tà&sO´Ä`æADNhnRxrph\x1±mÿ^.yK¶æÄW'Ì[§Î
^Ö°ìýæC÷þ!¢Î2.5FÑ
æù¡1%û
ñZÿë¨A¬ã¥/VDò#eáÀh)@e!¦p?dhª0HëDh¥{¢=d=T[iWOX²²Üé°x(PûMµÄ/^¼Z[ Yo<
³d§JÈQèWfÒjBA=_=3$¾×j§µG§tª*1ªz´òlbµ÷läf,T7Ée¼e£Z_îA%4Ïe2ÌBQt»òTD1×ñ9:æuæ
Äax $]bf`\òáyHd.`G=Ã(?ORVµéMB(4¬÷ecãt:#dÍEÂ
ú4²Y$ÒT~·üvßsY®ðaÁvHs ÿ*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%FFq,¥}þ²Lëä)ºÚVÙäÂT¢ì¾äÔñÆmD%ß°½=CcÌ
?þ®õ´ùù÷G梥M&}¾D:fQAg¸uÓÐ1ð?
Á»ý UØÁíqg(Ò
@>Ãè^
§Rn:ädt1©Ø©HG,í¸LXÊX¼ËUô-IÍcÂy¿]R£¸¼¬î6Êâ('øÙ¡.g1´Õpí@§ÚñÅÇyZb8æ>a"J?ªq¢ÿ~"«}n
ÃFåD#ÉZ«P
ØçARQ[2I¥\µý@ÏoIå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ä=¤óMFNm#_Zj"NnøYÏâhßE5xuù>iAvκ¥æĶpkú"â7¢aüIñ)_íl±
ºa¸7Â9_<´~ÈEp9QÄw|D"îò÷åTlTô;X9Kø=ñ5
¤2-SQ¹Dn¬Í¨'Cò
ÅÚò,©MLD^ãÓDevijB×~/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ÃøT2¦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&èÅNZa3ïÁ<ûz'¾ªÓK(Á¶¨»¤±Uýk®¯(ô()ñ8ÌÞ9 ÖØàºY&5÷uíEdå7Ôt¥fp¢#ã6³*étZ©-ñaT Z1§ªZ¦^r¦®¼NO@é]amûB>5Æ1/H§¥Â½Ê:â_¢ìâdÈP[éæcDdó[f G{}ÿ5*fT
ÁlÎE:í»0VOö((OSúBQ-Æ%jïwĪýçÌpªl´Wº|ÇîÂ*d9HÊm5' ¡¤#%)K°¬*6*[ÃSA|zóZWb0X%n8(¤J+ýX"Y0H¸ôBm¥.êh}ñÀjä6È˽ {åN&1~µà#Ru[ôü 1.ÑH¯ ,ÈÚ¹ï`Á¹H}RL-ôb`Ç&/*S, °á5*'A¹¼¿øÖC§¦¨i ÄêÉÙk×äj¤"ÓÙãjç*¸1É&épi×ÏmyÒ@¥ôêÜ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)üïüEVbMrlÀ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&ÄöªsJ§Øöêþ¦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/²¹\Aq`?\ÓZ
Ç=·¼ûdgfÍÛækHözc< µfåxpÝTa|ÿÔ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_AP¨Lϳ×Æ? t1ЫvxqØéW%
oÎÝ# LsMeõÐÓwÀ¹I*a±¢ø¦`Ág/÷p|-®y3K9iÅWFÙ7ÙeJMËm y¿Kì?ìFÛ)ÁË|Î,b§Y!ü*ûÒr1UO;!¸öíKV8 ¤äz_.Â^½Eªe¸ÓoB3¬¤M#
,¤ &ÖÃì"Fî%·!§¹&ccÂ{ÇȺôT.h-ÃQwù2ÉO0NÓIüyoOäè`bk¢<óBKXXóX%ÙÆïÆm=&
Äy#ÉáYiÁån#mÄcÁ³"®ÆÓÇ¢Àúè8ü©"Ò²4¥Ý©«·¸EMÿóMüeðZóM|Sp1Ø^{·'ú ´}xÒp¬¼¦É+5ê³Ú Fû(Zq÷®ì7g72o2ûòðvA°îYW,zÉ·«x²ô 7^ùwðx7âé8S¾ÈÉÁN Åëé!ãèÀ]·Ö¶lÖ#áéçXyù-÷JñãàJë|/Ý-±Q
©_#@L#ºH_IYLSfÀmVIDXbÝ"}µ0)ß%xäiË §çlÞ49
Êjû0®
È«,x¼TV²ªÑñ}JÀ;ª³t`%ÙÂ:ÞCiabAÚÜúaz@ÍS(&â23 ¢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-õ)v0--
6. (*þ"'éô
2®. $Ƥ4
{A*÷+|"-*.5$%áþÛ(×í&*½2(Âme$
,=MC$))´´0¶% *þS&U
:3DU[\Qoe|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#*[mlK>uÆ$ BhR2»,/01'a
*:klH@"- >#X[¬~/M#
mw þÀþå#¢¤
/;Ln!?e"]CT75'7+0$PJa9' dH#(;8þ#/&!f7
W!þA17R± þ©"
#.#(0K0KRx+%¬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'1o)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=7Lf:0þ¼') \1x.Jbo÷
j
*m^9;W1&.Y;6º#! "DIÎ5<$"*&6I·>@& :+!'"7/M3=B8\F3/O¨k/-_H#@@,!2V4B at 6<):§
&,C4&7)5`Zxu¹.#&0+~@6<%,
:17%&0)
3*!<p12#*[ì mlK>uÆ$ BhR2»,/01'a«j
3*!<p12#*[¢
«mlK>uÆ$ BhR2»,/01'a
j
3*!<p12#*[ $R'1mlK>uÆ$ BhR2»,/01'a«hh©
3*!<p12#*[)ÈmlK>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þxR162þ¦
.5G(
«w I.0-e5n: þ 2SnþÕH ¨
j
>!
)B=4¢
«ºnns WÚ!Cû3%
ºï
j
>!
)B=ÔȺnns WÚ!Cû3%
º.
(Ã-õ33ýo)2cðý×>"öþù
« %mZ57¯>g@CMufUfuE0Â
j*È£a8 UÍ4COFzg»<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þOlK>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-õ)v0--
6. (Âhh©Þ*þ"'éô
2®. $Ƥ4
V!!·þfyr"%Nh$Aþ¹A17R± þ©"
#<"& (+!ñ.
Pñ.>'J#z +/R0¢'1 (
,=MC$))´2&)Vþ·´0¶% *þS&U
G68
,=MC$))Q !"´´0¶% *þS&U
G68
36.4PI#**´´,¿;0;È%!#µU1SÃ;W
(xJ;{`9y~b?* $1$ ":PPÕ»þ¾%),¾&
M ZA"-')HE2! #K`&'`·Úvf!#þÜ{q#%Oh%DEI2Ow^
«Í
©Os\imcwbOp}kviE<cW5=C?\N;FÂ
h
hþàkÄ>G|n[ÁEa
4GQuH¶NRW_JÖ
«Í
©91SiFMeO8@>OT&!C=J*$=<"(î
h
hÁ9{1SIBS*K(-1[qs,/S.
*!:."$$A/5í (!0<$1(7ijD&R at F\+%/!$ =0%"$%/>' C?0$#4J+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
QF1!$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¢<LKBX¨<*
Úvf!#Ë25
SG1!$K`&'`CJl1"@þ
1<<J<;!!<DI1Ny^
Úvf!
0c®A
7T
«Í
©î
h
h
û
%Q
(Ã-o)2cðý×>"öþù
1'!þ'2
+º$
@þãB-õ)v0--
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-µ'#Nw56(
0$%>$²¬þ{"!G'/2Jb)&&=9DS-
co-H%<W/$
<> 83*'
vÐÏ 7
WI!K( KR
10(äÖ'þ¿m>2x/#»aj12
=-)
rA17R± þ©"
8>b" '>$]<LV!5bá«'-A¬,@ Ó55
GKKP!MO!u G0-hyf$S%(#M þÖ 0Á:zþÔL
þ
vR&&;0I
N#+V.>f27'sÈ)æ.>fO¦h
vXY
< j-µ'#Nw56(
§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-µ'#Nw56(
%QÁ9{1SIBS*K(-1[qs,/S.
%QD;O/m^?h*h]
< j-µ'#Nw56(
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
®@:GQuAE at RW_O
+(=./SFBHG|D4¹ks; %8@$.
:2VAG[
+(=C
:2VAG[ks; %8@$
RjNNþöNNýs5þËþÍ3%ý½Cý½
þ´Lý½
6VAJ7sJ%HS^C¨CI
%+b*3VáZ9?«ýU0;VBH].%ýqf#&:
þJ,F
F%
&<¼*@jCECjE=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
®
â,¼äü
4BV¦à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//Ú0200Ø1(1|1Ê22 at 2r2Î3$3h3¦44V4¬4ü5P5¨5ì6866Â6ò7*7^77Ò88f8°99P9z9Ä::J::Î;
;V;;¼<<H<|<<<º<Þ==x==²=Ö=ú>>:>V>h>z>>>°>Â>Ð>Þ>ò??? ?2?F?Z?f?~??º?Ö?ü@@6 at H@\@@@¬@¾@à@î@üAA(AJAnAA A¬A¾AÞAôBB<B`BBB®B¾BÎBâBîCC(CC²CÐCôDDFD\DDDÔEE<EREE¶EöFDFfF²FèG"GZGGÈHH>HdHHÀIIdI¶IîJ,JzJÂKKXKKàLLlLLâM.M~M¬MäNNBNtN¨NÎO.OZOOÊPP at PjPPÞQQDQQÀR
RDRRÐSStS¸SôT>T|TªTîU.UZUpU®UÄV
m at mm´mðn*nxnÄoojoÆppXp pðq<qqÀqòr(rTr~rrr¤r²rÀrÎrâröss:s\s|s¸söt2tltt¤tÔu`vv&vJv|vv¸vêvüww at wNwhwtwwüx(x(xpx®xÂxÖxâyy"yPyy´yÊyúzzz,zZzlzz´zÔzò{Z{{Ü|D|Ð}:}^}}ê~H~º.lv¨
:lÐü(TÊòDl¾îr¼L
H|¢¶Úî"B|ê ô¡¤¢¢ú£~¤
NnÌ®
®(®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#GQ#+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[:!!JT ( &*;0%$?1)
.>VaKB,ÎB88¬4!2-&1$;
yYE3=*.`NVm)
QA2B;&5A0tÏqUa|gi$8O(A@))2þgW@)VS5c
."û` ý£RVF:û%þ½"-ç'þ©#
."û aþ7RVF:û%þ½"-ç'þ©#
."ûgg§ýRVF:û%þ½"-ç'þ©#
."P((ýÄRVF:û%þ½"-ç'þ©#
'O9 &*(Í pa.û a9þgjW!,JXú
Y7D#8B{ F§þÕPrcm0(¸!þ¡ò-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
í41~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þ;Pn68fcU=þù.YàOP"LX9
4 6M25(9($1 -+)04
ÿ'Ä
1"þ
'®!N*; -F;Pn68fcU=þù.YàOP"LX9
*( (-1`=^§,%"/ý6ZA&*Dþt,fN>V143TG
ÿ&¼É
4 6M25(9($1 -+)04
$ 12(L1< 8«)%IDC/PEI@^.# $ ÒZW(#?
ÿ'ÄÌ
1"þ
#A3-!
ãU$ÈCW%)6Ê
]2)
$ 12(H¡<2*#!«&+4!(''/ N5sD_-$ $ =l$53 ?
%/5^G3^ D>RA;!$8w]#KBTFNG1
$ 12(LHSZHM`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**UPO?pP%?@
-3IÃÃG<Uqs[b'r> Oq
-3IÃÃG<UQs[b'r> Oq
ÿòº
'/9 #`i¹nX`Ua#9Øþ¼%?+3 )+.A07%!9Þó;R
Ó8)B¤8" ;º
2&$FH#%'%#}3.D"$
(
P¦,&#\þþP&
+$;B(8'<=&@;$+
/#'6#/&/!< Ó9:ÓH!,G34HC##&$'HF,17»:! ;º &1
HF'$&##CH43G,
"W6FlZì!\M at W
Ó8)B¤8" ;º
2&$FH#%'%#}3.D"$
(
'/9 !8%AÌþ¼%?+3 )+.A07%!9þK5#
ö$8þJ5$
P¦,&#\þþP&
%@1%;±þî*
9 9!9É.=]|; ;sB5
Ü.#AþX9!
01 :£ 7;:!9µ5#(Aþ*â6 %@þ)à7$#@þV:
aüø8!!8
01 :£ 7;:#e!9µ5#(Aþ*â6 %@þ)à7$#@þV:
J_+2(74>5"67?[rfim-]kR6] yÞr at 2UJM2c
KZ&Ð))m5<3þÄ*5
|#~ )H
6(3296
"9!$$(7$¤'6#~0
@(3376
39&Ð)82
5<5þÂ*75<
)@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?çigh]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 Z68-%-_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@5S
£%%
0 "9G5V@^(,%x"es/#!=70©H3(3h10c)7õk¨G;k¨;¥@&
#,5 X
à¯A¥þ°Nþñ_eC_?þåN/bò"þÑþñçã>ýÂ
=/VE
E5*
24&5
WF9@$6"J)2N=VgI@ÜWt\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ÉË-Ty'07(v
,BHXnQd
?dÃK*XkNB^7#0'5]mIBDf[}~okiTKIH
v¬>15=Aj7I>q
8xB1F<&/"(O?R`?4DlYPO{
OC9Qc|Ce
TI<NA88WYÍJ=N6 at 88!dT-&!% þæ96 %$`6F6Ef
d·'%P8YLa0 áþ°-#,&¾½&6& XÛÄJW"c5K)2
-!% f
ò¶þÈY-.1 `}Q?
DnG'0""$[25\8|6&ßÎ;:.-^1&>D3},%\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
Teþ>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
þþ³³
TxL?>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/M16X%
ý0lF88}X61Mým/
%
/ýmM16
qgýu
%X61M
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
67
41&ýª7
41&T
67ý¬&1
//1ýÓo8h{sq|32Bý6
r|{r@B(5
3=T
5/1ËÈÀs¡¡r2 nn3ÉÉ
7~.2¢æ¢BÈË1nn 2r¡¡sÉÉ3
36B3#þf3#4
36
Z7U)%-¥¥B"!(
"L
,N
v* #Mc"!(!"(
f0X)1 ¥¥BB¥¥ -%)U7Z^(!"
Z7U)%-B"!(
Z7U(*!þ
;:ýyö *)h?yIf
f0X)1 B(!"B -%)U7Z
f0X)1 B(!"B!ç)fIy?h)* öýy:;þ
!
"L
,N
v* #Mc"!(!"
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|3po6
)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ým3|qsþC{sq|6mý6
0,5¤¤
//1¢¢B¤¤5,
2%:¢¢1//
0,5¤¤-5
0,5¤¤¤¤
3Fp
ý´[NÜ:h
?x'.6
ýæ
3[N
?B:h5'.6
þ¢
3Fp
^
3[Nþ/[N
?:h
?:h5'.6'.6
J`ÞoýjÇÇþýþÔ
>.ã.?
9&W[#"þ""Z
&<¼*@jCECjE=F!%IpC6Wqþü1ht[;\Fm¯0'MhþÁ,*µkbvT7
ªkIB[5
Bþ[5Ò´k
rt
X *M
Q%;'pp'T!!U'ts'Q
#C5±*¹qr]
°þP5
þíA××ýË5þP°
Mn;1E
Mn;1ÜÚ"WZpE]#?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=[HK/6üUmiFNU at VÖU
ÀBþöB
A××ÙB
A××ëBþ÷B
A××þMLLBBLLB
AÖØ?
AÖØ
þí?ØÖAþï
¥ðÏMAaÂ+T6
ªäÄIAZÔ?ÝÈ\@
Øl-o
A××wJ@@N<Q{@@
1<!YF>ùK26*¬Â:üþô=S@XÒ°
2JB±ÇN<Q{ßy
¡µTAaÐ}?Û9"/WC
ÌgØ7
óA·ºèó?¹¸Aô
ôA¸¹
|QCT·cBjuæMAþ£?
h¯wByrC¶ÊV
f°vBxtU!Hþ±(#
"Qr£Bþ_¨R8f
ýR9f
"bÀ_TxR?g~~Bd]r£{{BþXBYBRBs
ÕAìBþsÕ?AÖ
ÖA
CÓÔ
B
"q2BNþÙ4?CN6:aCf?m;õU
B:>Ì6
ì
þðÔYËýbù¢>BÅg
lùbýËYÔh_gÅB>
;ïéþgBþl
éþgBþl
4\p
k^4
;Jþ. þ¤/!is(!u
$"-E% )"! xE==EwX[)G0+>>
':>>+0G)
"@nHDc60IE)/O4*n)c¦¤vM at AH6]8RaHlH^¢¯gaR8]6HA at Mv¤¦c
=?[Z@@Z[?ýàEdcFGef
ÇA+)$;;'V,()6þoRvvRQww
0*6
1(8B/2
1(8/2/
0,4.2
Z7U)%-B"!(
Z7U)%-B"!(B 1)X0f
"L
,N
v* #Mc"!(!"
5-¤
1(8£¤-5
Z7U)%-¥þ"!(
f0X)1 ¥T¥ -%)U7Z^(!"
Z7U)%-B"!(
f0X)1 BB -%)U7Z^(!"
"L
¥¥,N
v* ¥¥#Mc"!(!"(
Z7U)%-þ¨"!(
f0X)1 + -%)U7Z^(!"
1D:4½Bl41!}ps
1
/2/iµµ´;{sp}!14lB½4:
/2/iµ
1%;´µµi/2
I/lh)+O#9~S>"àzc³6#
o¦?>L#q4f»
PWn[~9#r
#6³czà">»f4q#L>?¦
#6³]yÚ¢[~9#O+)hl/I
OX
z/I
OXo[~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+]
+Bq¶-±5þÿÏ]r
" iý"0þMÍ%!1^*# ##.Y*'þÒ#-þL
$11,%jY%
]Xeþ3%F+
#C5[ $Ye>*8 ]qr]
%Yj&+11$
Z(¤ÏF#
+Bq] 8*>eY$ [5þÿÏ]r
Z(þ½ÉAaeX]
þÛÏF=$ [5C#
+Bq] 8*>eÏ]
$11+&jY%
]XeaA*þÛFúþ½5[ $Ye>*8 ]qB+
#Br]
+Bq] 8*>eþdÏ]r2Yj&+11$
Z(þ½ÉAaeX]
þXÏF
#þ½5[ $Ye>*8 ]qB+mr]ÌAþ7C(Z
$11+&jY%
]Xeaþ%F
#C5[ þ÷Ïr3%
]XeaAÉþ½(Z
$11,%jÏþÛ
+Bq] 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<"U0'?"K5Aoi(&=YY0)%~"/#)eF@*,&Xjq*L8,"=P6V kG7;\&
>'0Û¥
9IMgIIHvJuV~äL
$)57+1TÂyM`"F2^|T+*B=#LtÊ<+/ve?OÞ«}`$CbDk&+!V?K0D8j!©H3(3iMHd?0c)7ùq¦G;k¨;
9IMgII_¾;*0R &uV~äL
A;uOOu2lD)·~!$(3[Û2ÂþÓPz¸WþÙþÁ\NHS<R5++ kG7;\11sÎÍipiID*M&wÎ
F7~;X|D]-*X&O49DwVYléÆ ±MA:BPI31 MOA)* 6gfXE*5j=FD:Pmq
>C8C[8IFA`'"Q5# ;*n7q¹0%y;2-2OFZG
3FB¨+-35)4e,f':Q
8 7,3!B?)4cC7s#UCWí& \K}^à4,mc9PC[ hn3*@ÐX©^ci
=;4BX$("7\4ánm?6.-EI`O'!
¡.e
>C8B$*¬Ì0)q2W);N@[¯ý²¬_Z(=<81.5OFZ''Dþ¾)TI}@)I<
ECI #Z` þK_W+'?fmOþÙ(&N
"?'þó79E]` Éþ"?3+0 +"$þ(
2>QtY=P
PWDFh)tg"&1`Wþ&"?Aê±Q$ì¯V.b!#'!1N&
PWHIg!(*gXý%#<D
Ê5V.b!0C¤'*
QVH03G²//#ÕC(Éþú'.a
@A9<$0Dþç6_nV=`ÊF+X, 6:7:/03OEZ! 0ýó"÷º*}>£
PWHxÖ+"T-70À
Z2bä )0 J$
!HV'fgGOHþ}M`bX*';i\2uI
PWKGÇUN "a¾]X' -+5V~.bþÙ''ä±þ×* O
PWK37- %Ò lj2/<f\,bþÙ$"hi$$8U2$`
PWKcbK37- %Ò lj2/s2/<f\,bþÙ$"å°þÙ$"hi$$8U2$`
¥,
PWK!GÇUþù79E]¨þ¸gC"r+0 ±-+5V~.bþÙ'' ä±þ(
2G%²M9tþÓýº!
þ~ù0þ>S
·
ÕÎ
ª"
c., with final additions and corrections provided by Coen Hoffman, Elsevier (retired)
i
o
!"#$%&'
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopuni00A0uni00ADuni0100uni0101uni0102uni0103uni0104uni0105uni0106uni0107uni0108uni0109uni010Auni010Buni010Cuni010Duni010Euni010Funi0110uni0111uni0112uni0113uni0116uni0117uni011Cuni011Duni0120uni0121uni0122uni0124uni0125uni0126uni0127uni0128uni0129uni012Auni012Buni0130uni0132uni0133uni0134uni0135uni0138uni013Duni013Euni013Funi0140uni014Auni014Buni014Cuni014Duni0150uni0151uni015Auni015Buni015Cuni015Duni0165uni0166uni0167uni0168uni0169uni016Auni016Buni016Euni016Funi0170uni0171uni0174uni0175uni0176uni0177uni0179uni017Auni017Buni017Cuni017Funi0180uni0188uni0190uni0195uni0199uni019Auni019Buni019Euni01A0uni01A1uni01A5uni01AAuni01ABuni01ADuni01B5uni01BAuni01BBuni01BEuni01C0uni01C1uni01C2u!
ni01C3uni01F0uni0221uni02B9uni02BAuni02BBuni02BCuni02BDuni02BEuni02BFuni02C2uni02C3uni02C4uni02C5uni02C8uni02C9uni02CAuni02CBuni02CCuni02CDuni02CEuni02CFuni02D6uni02D7uni02DFuni02ECuni02EDuni02F7uni0300uni0301uni0302uni0303uni0304uni0305uni0306uni0307uni0308uni030Auni030Buni030Cuni030Duni030Euni030Funi0311uni0312uni0313uni0314uni0315uni031Auni031Buni031Duni031Euni031Funi0320uni0327uni033Funi0359uni035Cuni0360uni0361uni0362uni037Euni0384uni0385uni0387uni0391uni0392uni0393uni0394uni0395uni0396uni0397uni0398uni0399uni039Auni039Buni039Cuni039Duni039Euni039Funi03A0uni03A1uni03A3uni03A4uni03A5uni03A6uni03A7uni03A8uni03A9uni03AAuni03ABuni03ACuni03ADuni03AEuni03AFuni03B0uni03B1uni03B2uni03B3uni03B4uni03B5uni03B6uni03B7uni03B8uni03B9uni03BAuni03BBuni03BCuni03BDuni03BEuni03BFuni03C0uni03C1uni03C2uni03C3uni03C4uni03C5uni03C6uni03C7uni03C8uni03C9uni03CAuni03CBuni03!
CCuni03CDuni03CEuni03D0uni03D1uni03D2uni03D5uni03D6uni03D8uni
03D9uni03DAuni03DBuni03DCuni03DDuni03DEuni03DFuni03E0uni03E1uni03F0uni03F1uni03F4uni03F5uni03F6uni0401uni0402uni0403uni0404uni0405uni0406uni0407uni0408uni0409uni040Auni040Buni040Cuni040Euni040Funi0410uni0411uni0412uni0413uni0414uni0415uni0416uni0417uni0418uni0419uni041Auni041Buni041Cuni041Duni041Euni041Funi0420uni0421uni0422uni0423uni0424uni0425uni0426uni0427uni0428uni0429uni042Auni042Buni042Cuni042Duni042Euni042Funi0430uni0431uni0432uni0433uni0434uni0435uni0436uni0437uni0438uni0439uni043Auni043Buni043Cuni043Duni043Euni043Funi0440uni0441uni0442uni0443uni0444uni0445uni0446uni0447uni0448uni0449uni044Auni044Buni044Cuni044Duni044Euni044Funi0451uni0452uni0453uni0454uni0455uni0456uni0457uni0458uni0459uni045Auni045Buni045Cuni045Euni045Funi0462uni0463uni046Auni046Buni0472uni0473uni0474uni0475uni0490uni0491uni2010uni2011
figuredashuni2015uni2016uni2017uni201Buni201Ftwodotenleaderuni2031uni2032uni2033uni2034uni2035uni2036uni2037uni2038uni203Euni2040uni2043uni204Euni2052uni205F nsuperioruni20ACuni20D0uni20D1uni20D2uni20D6uni20D7uni20DBuni20DCuni20DDuni20DEuni20E1uni20E5uni20E6uni20E7uni20E8uni20E9uni20EAuni20EBuni20EEuni20EFuni20F0uni2102uni2107uni210Auni210Buni210Cuni210Duni210Euni210Funi2110uni2111uni2112uni2113uni2115uni2116uni2118uni2119uni211Auni211Buni211Cuni211Duni211Euni2124uni2125uni2126uni2127uni2128uni2129uni212Buni212Cuni212Duni212Euni212Funi2130uni2131uni2132uni2133uni2134uni2135uni2136uni2137uni2138uni213Cuni213Duni213Euni213Funi2140uni2145uni2146uni2147uni2148uni2149onethird twothirdsuni2155uni2156uni2157uni2158uni2159uni215Auni2190uni2191uni2192uni2193uni2194uni2195uni2196uni2197uni2198uni2199uni219Auni219Buni21A4uni21A5uni21A6uni21A7uni21AEuni21B0uni21B1uni21B!
2uni21B3uni21B4uni21B5uni21B6uni21B7uni21B9uni21BAuni21BBuni21C4uni21CDuni21CEuni21CFuni21D0uni21D1uni21D2uni21D3uni21D4uni21D5uni21E0uni21E1uni21E2uni21E3uni21E4uni21E5uni21E6uni21E7uni21E8uni21E9uni21F5uni21F7uni21F8uni21F9uni21FAuni21FDuni21FEuni21FFuni2200uni2201uni2202uni2203uni2204uni2205uni2206uni2207uni2208uni2209uni220Auni220Buni220Cuni220Duni220Funi2210uni2211uni2213uni2214uni2215uni2216uni2217uni2218uni2219uni221Duni221Euni221Funi2220uni2221uni2222uni2223uni2224uni2225uni2226uni2227uni2228uni2229uni222Auni222Buni222Cuni222Duni222Euni222Funi2230uni2231uni2232uni2233uni2234uni2235uni2236uni2237uni2238uni223Buni223Cuni223Duni223Euni223Funi2240uni2241uni2242uni2243uni2244uni2245uni2246uni2247uni2248uni2249uni224Auni224Buni224Cuni224Duni225Duni2260uni2261uni2262uni2263uni2264uni2265uni2266uni2267uni2268uni2269uni226Auni226Buni226Cuni226Duni226Eun!
i226Funi2270uni2271uni2272uni2273uni2274uni2275uni2276uni2277
uni2278uni2279uni227Auni227Buni227Cuni227Duni227Euni227Funi2280uni2281uni2282uni2283uni2284uni2285uni2286uni2287uni2288uni2289uni228Auni228Buni228Funi2290uni2291uni2292uni2293uni2294uni2295uni2296uni2297uni2298uni2299uni22A2uni22A3uni22A4uni22A5uni22A6uni22A7uni22A8uni22A9uni22ACuni22B2uni22B3uni22BAuni22BEuni22BFuni22C0uni22C1uni22C2uni22C3uni22C4uni22C5uni22C6uni22CEuni22CFuni22D5uni22DAuni22DBuni22DCuni22DDuni22DEuni22DFuni22E0uni22E1uni2300uni2305uni2308uni2309uni230Auni230Buni2312uni2316uni2320uni2321uni2322uni2323uni2329uni232Auni233Funi2340uni2393uni239Buni239Cuni239Duni239Euni239Funi23A0uni23A1uni23A2uni23A3uni23A4uni23A5uni23A6uni23A7uni23A8uni23A9uni23AAuni23ABuni23ACuni23ADuni23AEuni23AFuni23B0uni23B1uni23CEuni23D0uni23DCuni23DDuni23DEuni23DFuni23E0uni23E1uni23E4uni2605uni2606uni2609uni263Cuni263Duni263Euni263Funi2640uni2641uni2642uni2643uni26!
44uni2646uni2647uni2648uni2649uni26E2uni279Buni27F5uni27F6uni27F7uni27F8uni27F9uni27FAuni27FBuni27FCuni27FDuni27FEuni2902uni2903uni2904uni2906uni2907uni2912uni2913uni2934uni2935uni2936uni2937uni2938uni2939uni293Auni293Buni293Cuni293Duni293Euni293Funi2940uni2941uni2981uni299Buni299Cuni299Duni299Euni299Funi29A0uni29A1uni29A8uni29A9uni29AAuni29ABuni29ACuni29ADuni29AEuni29AFuni29BBuni29BFuni29C0uni29C1uni29E3uni29E7uni29FAuni29FCuni29FDuni2A00uni2A01uni2A02uni2A03uni2A04uni2A05uni2A06uni2A09uni2A0Auni2A0Buni2A0Cuni2A0Duni2A0Euni2A0Funi2A20uni2A2Funi2A3Funi2A42uni2A43uni2A7Duni2A7Euni2A95uni2A96uni2A99uni2A9Auni2A9Buni2A9Cuni2AFDuni2B50uni2B51uniA727u1D49Cu1D49Eu1D49Fu1D4A2u1D4A5u1D4A6u1D4A9u1D4AAu1D4ABu1D4ACu1D4AEu1D4AFu1D4B0u1D4B1u1D4B2u1D4B3u1D4B4u1D4B5u1D4B6u1D4B7u1D4B8u1D4B9u1D4BBu1D4BDu1D4BEu1D4BFu1D4C0u1D4C1u1D4C2u1D4C3u1D4C5u1D4C6u1D4C7u1D4C8u1D!
4C9u1D4CAu1D4CBu1D4CCu1D4CDu1D4CEu1D4CFu1D538u1D539u1D53Bu1D
53Cu1D53Du1D53Eu1D540u1D541u1D542u1D543u1D544u1D546u1D54Au1D54Bu1D54Cu1D54Du1D54Eu1D54Fu1D550
,,::HNd~°Êäþ
Index: modules/damieng/graphical_editor/daxe/lib/fonts/STIXSubset-Regular.ttf
+++ modules/damieng/graphical_editor/daxe/lib/fonts/STIXSubset-Regular.ttf
Ò
M
¼
¼
}
.ÿþ
Ò
ê
ô
ð
ù
'?Y\`~£ÐÕØðôQ^bjr " % 0 > @ C N R _ ¬ Ð Ö Û á å î!!!
!!!$!+!<!E!S!!¤!®!°!¹!Ä!Í!à!õ!÷!ý""";"]"`""¢"¬"²"º"¾"Î"Õ"Ú#
H
p
®
â,¼äü
4BV¦à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//Ú0200Ø1(1|1Ê22 at 2r2Î3$3h3¦44V4¬4ü5P5¨5ì6866Â6ò7*7^77Ò88f8°99P9z9Ä::J::Î;
;V;;¼<<H<|<<<º<Þ==x==²=Ö=ú>>:>V>h>z>>>°>Â>Ð>Þ>ò??? ?2?F?Z?f?~??º?Ö?ü@@6 at H@\@@@¬@¾@à@î@üAA(AJAnAA A¬A¾AÞAôBB<B`BBB®B¾BÎBâBîCC(CC²CÐCôDDFD\DDDÔEE<EREE¶EöFDFfF²FèG"GZGGÈHH>HdHHÀIIdI¶IîJ,JzJÂKKXKKàLLlLLâM.M~M¬MäNNBNtN¨NÎO.OZOOÊPP at PjPPÞQQDQQÀR
RDRRÐSStS¸SôT>T|TªTîU.UZUpU®UÄV
m at mm´mðn*nxnÄoojoÆppXp pðq<qqÀqòr(rTr~rrr¤r²rÀrÎrâröss:s\s|s¸söt2tltt¤tÔu`vv&vJv|vv¸vêvüww at wNwhwtwwüx(x(xpx®xÂxÖxâyy"yPyy´yÊyúzzz,zZzlzz´zÔzò{Z{{Ü|D|Ð}:}^}}ê~H~º.lv¨
:lÐü(TÊòDl¾îr¼L
H|¢¶Úî"B|ê ô¡¤¢¢ú£~¤
NnÌ®
®(®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#GQ#+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[:!!JT ( &*;0%$?1)
.>VaKB,ÎB88¬4!2-&1$;
yYE3=*.`NVm)
QA2B;&5A0tÏqUa|gi$8O(A@))2þgW@)VS5c
."û` ý£RVF:û%þ½"-ç'þ©#
."û aþ7RVF:û%þ½"-ç'þ©#
."ûgg§ýRVF:û%þ½"-ç'þ©#
."P((ýÄRVF:û%þ½"-ç'þ©#
'O9 &*(Í pa.û a9þgjW!,JXú
Y7D#8B{ F§þÕPrcm0(¸!þ¡ò-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
í41~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þ;Pn68fcU=þù.YàOP"LX9
4 6M25(9($1 -+)04
ÿ'Ä
1"þ
'®!N*; -F;Pn68fcU=þù.YàOP"LX9
*( (-1`=^§,%"/ý6ZA&*Dþt,fN>V143TG
ÿ&¼É
4 6M25(9($1 -+)04
$ 12(L1< 8«)%IDC/PEI@^.# $ ÒZW(#?
ÿ'ÄÌ
1"þ
#A3-!
ãU$ÈCW%)6Ê
]2)
$ 12(H¡<2*#!«&+4!(''/ N5sD_-$ $ =l$53 ?
%/5^G3^ D>RA;!$8w]#KBTFNG1
$ 12(LHSZHM`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**UPO?pP%?@
-3IÃÃG<Uqs[b'r> Oq
-3IÃÃG<UQs[b'r> Oq
ÿòº
'/9 #`i¹nX`Ua#9Øþ¼%?+3 )+.A07%!9Þó;R
Ó8)B¤8" ;º
2&$FH#%'%#}3.D"$
(
P¦,&#\þþP&
+$;B(8'<=&@;$+
/#'6#/&/!< Ó9:ÓH!,G34HC##&$'HF,17»:! ;º &1
HF'$&##CH43G,
"W6FlZì!\M at W
Ó8)B¤8" ;º
2&$FH#%'%#}3.D"$
(
'/9 !8%AÌþ¼%?+3 )+.A07%!9þK5#
ö$8þJ5$
P¦,&#\þþP&
%@1%;±þî*
9 9!9É.=]|; ;sB5
Ü.#AþX9!
01 :£ 7;:!9µ5#(Aþ*â6 %@þ)à7$#@þV:
aüø8!!8
01 :£ 7;:#e!9µ5#(Aþ*â6 %@þ)à7$#@þV:
J_+2(74>5"67?[rfim-]kR6] yÞr at 2UJM2c
KZ&Ð))m5<3þÄ*5
|#~ )H
6(3296
"9!$$(7$¤'6#~0
@(3376
39&Ð)82
5<5þÂ*75<
)@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?çigh]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 Z68-%-_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@5S
£%%
0 "9G5V@^(,%x"es/#!=70©H3(3h10c)7õk¨G;k¨;¥@&
#,5 X
à¯A¥þ°Nþñ_eC_?þåN/bò"þÑþñçã>ýÂ
=/VE
E5*
24&5
WF9@$6"J)2N=VgI@ÜWt\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ÉË-Ty'07(v
,BHXnQd
?dÃK*XkNB^7#0'5]mIBDf[}~okiTKIH
v¬>15=Aj7I>q
8xB1F<&/"(O?R`?4DlYPO{
OC9Qc|Ce
TI<NA88WYÍJ=N6 at 88!dT-&!% þæ96 %$`6F6Ef
d·'%P8YLa0 áþ°-#,&¾½&6& XÛÄJW"c5K)2
-!% f
ò¶þÈY-.1 `}Q?
DnG'0""$[25\8|6&ßÎ;:.-^1&>D3},%\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
Teþ>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
þþ³³
TxL?>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/M16X%
ý0lF88}X61Mým/
%
/ýmM16
qgýu
%X61M
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
67
41&ýª7
41&T
67ý¬&1
//1ýÓo8h{sq|32Bý6
r|{r@B(5
3=T
5/1ËÈÀs¡¡r2 nn3ÉÉ
7~.2¢æ¢BÈË1nn 2r¡¡sÉÉ3
36B3#þf3#4
36
Z7U)%-¥¥B"!(
"L
,N
v* #Mc"!(!"(
f0X)1 ¥¥BB¥¥ -%)U7Z^(!"
Z7U)%-B"!(
Z7U(*!þ
;:ýyö *)h?yIf
f0X)1 B(!"B -%)U7Z
f0X)1 B(!"B!ç)fIy?h)* öýy:;þ
!
"L
,N
v* #Mc"!(!"
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|3po6
)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ým3|qsþC{sq|6mý6
0,5¤¤
//1¢¢B¤¤5,
2%:¢¢1//
0,5¤¤-5
0,5¤¤¤¤
3Fp
ý´[NÜ:h
?x'.6
ýæ
3[N
?B:h5'.6
þ¢
3Fp
^
3[Nþ/[N
?:h
?:h5'.6'.6
J`ÞoýjÇÇþýþÔ
>.ã.?
9&W[#"þ""Z
&<¼*@jCECjE=F!%IpC6Wqþü1ht[;\Fm¯0'MhþÁ,*µkbvT7
ªkIB[5
Bþ[5Ò´k
rt
X *M
Q%;'pp'T!!U'ts'Q
#C5±*¹qr]
°þP5
þíA××ýË5þP°
Mn;1E
Mn;1ÜÚ"WZpE]#?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=[HK/6üUmiFNU at VÖU
ÀBþöB
A××ÙB
A××ëBþ÷B
A××þMLLBBLLB
AÖØ?
AÖØ
þí?ØÖAþï
¥ðÏMAaÂ+T6
ªäÄIAZÔ?ÝÈ\@
Øl-o
A××wJ@@N<Q{@@
1<!YF>ùK26*¬Â:üþô=S@XÒ°
2JB±ÇN<Q{ßy
¡µTAaÐ}?Û9"/WC
ÌgØ7
óA·ºèó?¹¸Aô
ôA¸¹
|QCT·cBjuæMAþ£?
h¯wByrC¶ÊV
f°vBxtU!Hþ±(#
"Qr£Bþ_¨R8f
ýR9f
"bÀ_TxR?g~~Bd]r£{{BþXBYBRBs
ÕAìBþsÕ?AÖ
ÖA
CÓÔ
B
"q2BNþÙ4?CN6:aCf?m;õU
B:>Ì6
ì
þðÔYËýbù¢>BÅg
lùbýËYÔh_gÅB>
;ïéþgBþl
éþgBþl
4\p
k^4
;Jþ. þ¤/!is(!u
$"-E% )"! xE==EwX[)G0+>>
':>>+0G)
"@nHDc60IE)/O4*n)c¦¤vM at AH6]8RaHlH^¢¯gaR8]6HA at Mv¤¦c
=?[Z@@Z[?ýàEdcFGef
ÇA+)$;;'V,()6þoRvvRQww
0*6
1(8B/2
1(8/2/
0,4.2
Z7U)%-B"!(
Z7U)%-B"!(B 1)X0f
"L
,N
v* #Mc"!(!"
5-¤
1(8£¤-5
Z7U)%-¥þ"!(
f0X)1 ¥T¥ -%)U7Z^(!"
Z7U)%-B"!(
f0X)1 BB -%)U7Z^(!"
"L
¥¥,N
v* ¥¥#Mc"!(!"(
Z7U)%-þ¨"!(
f0X)1 + -%)U7Z^(!"
1D:4½Bl41!}ps
1
/2/iµµ´;{sp}!14lB½4:
/2/iµ
1%;´µµi/2
I/lh)+O#9~S>"àzc³6#
o¦?>L#q4f»
PWn[~9#r
#6³czà">»f4q#L>?¦
#6³]yÚ¢[~9#O+)hl/I
OX
z/I
OXo[~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+]
+Bq¶-±5þÿÏ]r
" iý"0þMÍ%!1^*# ##.Y*'þÒ#-þL
$11,%jY%
]Xeþ3%F+
#C5[ $Ye>*8 ]qr]
%Yj&+11$
Z(¤ÏF#
+Bq] 8*>eY$ [5þÿÏ]r
Z(þ½ÉAaeX]
þÛÏF=$ [5C#
+Bq] 8*>eÏ]
$11+&jY%
]XeaA*þÛFúþ½5[ $Ye>*8 ]qB+
#Br]
+Bq] 8*>eþdÏ]r2Yj&+11$
Z(þ½ÉAaeX]
þXÏF
#þ½5[ $Ye>*8 ]qB+mr]ÌAþ7C(Z
$11+&jY%
]Xeaþ%F
#C5[ þ÷Ïr3%
]XeaAÉþ½(Z
$11,%jÏþÛ
+Bq] 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<"U0'?"K5Aoi(&=YY0)%~"/#)eF@*,&Xjq*L8,"=P6V kG7;\&
>'0Û¥
9IMgIIHvJuV~äL
$)57+1TÂyM`"F2^|T+*B=#LtÊ<+/ve?OÞ«}`$CbDk&+!V?K0D8j!©H3(3iMHd?0c)7ùq¦G;k¨;
9IMgII_¾;*0R &uV~äL
A;uOOu2lD)·~!$(3[Û2ÂþÓPz¸WþÙþÁ\NHS<R5++ kG7;\11sÎÍipiID*M&wÎ
F7~;X|D]-*X&O49DwVYléÆ ±MA:BPI31 MOA)* 6gfXE*5j=FD:Pmq
>C8C[8IFA`'"Q5# ;*n7q¹0%y;2-2OFZG
3FB¨+-35)4e,f':Q
8 7,3!B?)4cC7s#UCWí& \K}^à4,mc9PC[ hn3*@ÐX©^ci
=;4BX$("7\4ánm?6.-EI`O'!
¡.e
>C8B$*¬Ì0)q2W);N@[¯ý²¬_Z(=<81.5OFZ''Dþ¾)TI}@)I<
ECI #Z` þK_W+'?fmOþÙ(&N
"?'þó79E]` Éþ"?3+0 +"$þ(
2>QtY=P
PWDFh)tg"&1`Wþ&"?Aê±Q$ì¯V.b!#'!1N&
PWHIg!(*gXý%#<D
Ê5V.b!0C¤'*
QVH03G²//#ÕC(Éþú'.a
@A9<$0Dþç6_nV=`ÊF+X, 6:7:/03OEZ! 0ýó"÷º*}>£
PWHxÖ+"T-70À
Z2bä )0 J$
!HV'fgGOHþ}M`bX*';i\2uI
PWKGÇUN "a¾]X' -+5V~.bþÙ''ä±þ×* O
PWK37- %Ò lj2/<f\,bþÙ$"hi$$8U2$`
PWKcbK37- %Ò lj2/s2/<f\,bþÙ$"å°þÙ$"hi$$8U2$`
¥,
PWK!GÇUþù79E]¨þ¸gC"r+0 ±-+5V~.bþÙ'' ä±þ(
2G%²M9tþÓýº!
þ~ù0þ>S
·
ÕÎ
ª"
c., with final additions and corrections provided by Coen Hoffman, Elsevier (retired)
i
o
!"#$%&'
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopuni00A0uni00ADuni0100uni0101uni0102uni0103uni0104uni0105uni0106uni0107uni0108uni0109uni010Auni010Buni010Cuni010Duni010Euni010Funi0110uni0111uni0112uni0113uni0116uni0117uni011Cuni011Duni0120uni0121uni0122uni0124uni0125uni0126uni0127uni0128uni0129uni012Auni012Buni0130uni0132uni0133uni0134uni0135uni0138uni013Duni013Euni013Funi0140uni014Auni014Buni014Cuni014Duni0150uni0151uni015Auni015Buni015Cuni015Duni0165uni0166uni0167uni0168uni0169uni016Auni016Buni016Euni016Funi0170uni0171uni0174uni0175uni0176uni0177uni0179uni017Auni017Buni017Cuni017Funi0180uni0188uni0190uni0195uni0199uni019Auni019Buni019Euni01A0uni01A1uni01A5uni01AAuni01ABuni01ADuni01B5uni01BAuni01BBuni01BEuni01C0uni01C1uni01C2u!
ni01C3uni01F0uni0221uni02B9uni02BAuni02BBuni02BCuni02BDuni02BEuni02BFuni02C2uni02C3uni02C4uni02C5uni02C8uni02C9uni02CAuni02CBuni02CCuni02CDuni02CEuni02CFuni02D6uni02D7uni02DFuni02ECuni02EDuni02F7uni0300uni0301uni0302uni0303uni0304uni0305uni0306uni0307uni0308uni030Auni030Buni030Cuni030Duni030Euni030Funi0311uni0312uni0313uni0314uni0315uni031Auni031Buni031Duni031Euni031Funi0320uni0327uni033Funi0359uni035Cuni0360uni0361uni0362uni037Euni0384uni0385uni0387uni0391uni0392uni0393uni0394uni0395uni0396uni0397uni0398uni0399uni039Auni039Buni039Cuni039Duni039Euni039Funi03A0uni03A1uni03A3uni03A4uni03A5uni03A6uni03A7uni03A8uni03A9uni03AAuni03ABuni03ACuni03ADuni03AEuni03AFuni03B0uni03B1uni03B2uni03B3uni03B4uni03B5uni03B6uni03B7uni03B8uni03B9uni03BAuni03BBuni03BCuni03BDuni03BEuni03BFuni03C0uni03C1uni03C2uni03C3uni03C4uni03C5uni03C6uni03C7uni03C8uni03C9uni03CAuni03CBuni03!
CCuni03CDuni03CEuni03D0uni03D1uni03D2uni03D5uni03D6uni03D8uni
03D9uni03DAuni03DBuni03DCuni03DDuni03DEuni03DFuni03E0uni03E1uni03F0uni03F1uni03F4uni03F5uni03F6uni0401uni0402uni0403uni0404uni0405uni0406uni0407uni0408uni0409uni040Auni040Buni040Cuni040Euni040Funi0410uni0411uni0412uni0413uni0414uni0415uni0416uni0417uni0418uni0419uni041Auni041Buni041Cuni041Duni041Euni041Funi0420uni0421uni0422uni0423uni0424uni0425uni0426uni0427uni0428uni0429uni042Auni042Buni042Cuni042Duni042Euni042Funi0430uni0431uni0432uni0433uni0434uni0435uni0436uni0437uni0438uni0439uni043Auni043Buni043Cuni043Duni043Euni043Funi0440uni0441uni0442uni0443uni0444uni0445uni0446uni0447uni0448uni0449uni044Auni044Buni044Cuni044Duni044Euni044Funi0451uni0452uni0453uni0454uni0455uni0456uni0457uni0458uni0459uni045Auni045Buni045Cuni045Euni045Funi0462uni0463uni046Auni046Buni0472uni0473uni0474uni0475uni0490uni0491uni2010uni2011
figuredashuni2015uni2016uni2017uni201Buni201Ftwodotenleaderuni2031uni2032uni2033uni2034uni2035uni2036uni2037uni2038uni203Euni2040uni2043uni204Euni2052uni205F nsuperioruni20ACuni20D0uni20D1uni20D2uni20D6uni20D7uni20DBuni20DCuni20DDuni20DEuni20E1uni20E5uni20E6uni20E7uni20E8uni20E9uni20EAuni20EBuni20EEuni20EFuni20F0uni2102uni2107uni210Auni210Buni210Cuni210Duni210Euni210Funi2110uni2111uni2112uni2113uni2115uni2116uni2118uni2119uni211Auni211Buni211Cuni211Duni211Euni2124uni2125uni2126uni2127uni2128uni2129uni212Buni212Cuni212Duni212Euni212Funi2130uni2131uni2132uni2133uni2134uni2135uni2136uni2137uni2138uni213Cuni213Duni213Euni213Funi2140uni2145uni2146uni2147uni2148uni2149onethird twothirdsuni2155uni2156uni2157uni2158uni2159uni215Auni2190uni2191uni2192uni2193uni2194uni2195uni2196uni2197uni2198uni2199uni219Auni219Buni21A4uni21A5uni21A6uni21A7uni21AEuni21B0uni21B1uni21B!
2uni21B3uni21B4uni21B5uni21B6uni21B7uni21B9uni21BAuni21BBuni21C4uni21CDuni21CEuni21CFuni21D0uni21D1uni21D2uni21D3uni21D4uni21D5uni21E0uni21E1uni21E2uni21E3uni21E4uni21E5uni21E6uni21E7uni21E8uni21E9uni21F5uni21F7uni21F8uni21F9uni21FAuni21FDuni21FEuni21FFuni2200uni2201uni2202uni2203uni2204uni2205uni2206uni2207uni2208uni2209uni220Auni220Buni220Cuni220Duni220Funi2210uni2211uni2213uni2214uni2215uni2216uni2217uni2218uni2219uni221Duni221Euni221Funi2220uni2221uni2222uni2223uni2224uni2225uni2226uni2227uni2228uni2229uni222Auni222Buni222Cuni222Duni222Euni222Funi2230uni2231uni2232uni2233uni2234uni2235uni2236uni2237uni2238uni223Buni223Cuni223Duni223Euni223Funi2240uni2241uni2242uni2243uni2244uni2245uni2246uni2247uni2248uni2249uni224Auni224Buni224Cuni224Duni225Duni2260uni2261uni2262uni2263uni2264uni2265uni2266uni2267uni2268uni2269uni226Auni226Buni226Cuni226Duni226Eun!
i226Funi2270uni2271uni2272uni2273uni2274uni2275uni2276uni2277
uni2278uni2279uni227Auni227Buni227Cuni227Duni227Euni227Funi2280uni2281uni2282uni2283uni2284uni2285uni2286uni2287uni2288uni2289uni228Auni228Buni228Funi2290uni2291uni2292uni2293uni2294uni2295uni2296uni2297uni2298uni2299uni22A2uni22A3uni22A4uni22A5uni22A6uni22A7uni22A8uni22A9uni22ACuni22B2uni22B3uni22BAuni22BEuni22BFuni22C0uni22C1uni22C2uni22C3uni22C4uni22C5uni22C6uni22CEuni22CFuni22D5uni22DAuni22DBuni22DCuni22DDuni22DEuni22DFuni22E0uni22E1uni2300uni2305uni2308uni2309uni230Auni230Buni2312uni2316uni2320uni2321uni2322uni2323uni2329uni232Auni233Funi2340uni2393uni239Buni239Cuni239Duni239Euni239Funi23A0uni23A1uni23A2uni23A3uni23A4uni23A5uni23A6uni23A7uni23A8uni23A9uni23AAuni23ABuni23ACuni23ADuni23AEuni23AFuni23B0uni23B1uni23CEuni23D0uni23DCuni23DDuni23DEuni23DFuni23E0uni23E1uni23E4uni2605uni2606uni2609uni263Cuni263Duni263Euni263Funi2640uni2641uni2642uni2643uni26!
44uni2646uni2647uni2648uni2649uni26E2uni279Buni27F5uni27F6uni27F7uni27F8uni27F9uni27FAuni27FBuni27FCuni27FDuni27FEuni2902uni2903uni2904uni2906uni2907uni2912uni2913uni2934uni2935uni2936uni2937uni2938uni2939uni293Auni293Buni293Cuni293Duni293Euni293Funi2940uni2941uni2981uni299Buni299Cuni299Duni299Euni299Funi29A0uni29A1uni29A8uni29A9uni29AAuni29ABuni29ACuni29ADuni29AEuni29AFuni29BBuni29BFuni29C0uni29C1uni29E3uni29E7uni29FAuni29FCuni29FDuni2A00uni2A01uni2A02uni2A03uni2A04uni2A05uni2A06uni2A09uni2A0Auni2A0Buni2A0Cuni2A0Duni2A0Euni2A0Funi2A20uni2A2Funi2A3Funi2A42uni2A43uni2A7Duni2A7Euni2A95uni2A96uni2A99uni2A9Auni2A9Buni2A9Cuni2AFDuni2B50uni2B51uniA727u1D49Cu1D49Eu1D49Fu1D4A2u1D4A5u1D4A6u1D4A9u1D4AAu1D4ABu1D4ACu1D4AEu1D4AFu1D4B0u1D4B1u1D4B2u1D4B3u1D4B4u1D4B5u1D4B6u1D4B7u1D4B8u1D4B9u1D4BBu1D4BDu1D4BEu1D4BFu1D4C0u1D4C1u1D4C2u1D4C3u1D4C5u1D4C6u1D4C7u1D4C8u1D!
4C9u1D4CAu1D4CBu1D4CCu1D4CDu1D4CEu1D4CFu1D538u1D539u1D53Bu1D
53Cu1D53Du1D53Eu1D540u1D541u1D542u1D543u1D544u1D546u1D54Au1D54Bu1D54Cu1D54Du1D54Eu1D54Fu1D550
,,::HNd~°Êäþ
Index: modules/damieng/graphical_editor/daxe/lib/src/attribute_dialog.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/attribute_dialog.dart
/*
This file is part of Daxe.
Daxe is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Daxe is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Daxe. If not, see <http://www.gnu.org/licenses/>.
*/
part of daxe;
class AttributeDialog {
DaxeNode el;
x.Element ref;
List<x.Element> attRefs;
HashMap<x.Element, SimpleTypeControl> controls;
HashMap<DaxeAttr, h.TextInputElement> unknownAttributeFields;
ActionFunction okfct;
AttributeDialog(this.el, [this.okfct]) {
ref = el.ref;
controls = new HashMap<x.Element, SimpleTypeControl>();
unknownAttributeFields = null;
}
void show() {
h.DivElement div1 = new h.DivElement();
div1.id = 'attributes_dlg';
div1.classes.add('dlg1');
h.DivElement div2 = new h.DivElement();
div2.classes.add('dlg2');
h.DivElement div3 = new h.DivElement();
div3.classes.add('dlg3');
h.DivElement title = new h.DivElement();
title.classes.add('dlgtitle');
title.text = doc.cfg.elementTitle(ref);
div3.append(title);
h.FormElement form = new h.FormElement();
h.TableElement table = new h.TableElement();
attRefs = doc.cfg.elementAttributes(ref);
SimpleTypeControl toFocus = null;
for (x.Element attref in attRefs) {
h.TableRowElement tr = new h.TableRowElement();
h.TableCellElement td = new h.TableCellElement();
String attdoc = doc.cfg.attributeDocumentation(ref, attref);
if (attdoc != null) {
h.ButtonElement bHelp = new h.ButtonElement();
bHelp.attributes['type'] = 'button';
bHelp.classes.add('help');
bHelp.value = '?';
bHelp.text = '?';
bHelp.title = attdoc;
bHelp.onClick.listen((h.Event event) => help(attref, ref));
td.append(bHelp);
}
tr.append(td);
td = new h.TableCellElement();
String name = doc.cfg.attributeQualifiedName(ref, attref);
String title = doc.cfg.attributeTitle(ref, attref);
td.appendText(title);
if (doc.cfg.requiredAttribute(ref, attref))
td.classes.add('required');
else
td.classes.add('optional');
tr.append(td);
td = new h.TableCellElement();
String value = el.getAttribute(name);
String defaultValue = doc.cfg.defaultAttributeValue(attref);
if (value == null) {
if (defaultValue != null)
value = defaultValue;
else
value = '';
}
SimpleTypeControl control = new SimpleTypeControl.forAttribute(ref, attref, value);
controls[attref] = control;
List<String> values = doc.cfg.attributeValues(attref);
if (values == null || values.length == 0)
if (toFocus == null)
toFocus = control;
td.append(control.html());
tr.append(td);
table.append(tr);
}
for (DaxeAttr att in el.attributes) {
bool found = false;
for (x.Element attref in controls.keys) {
if (att.localName == doc.cfg.attributeName(attref) &&
att.namespaceURI == doc.cfg.attributeNamespace(attref)) {
found = true;
break;
}
}
if (!found) {
if (unknownAttributeFields == null)
unknownAttributeFields = new HashMap<DaxeAttr, h.TextInputElement>();
h.TextInputElement input = new h.TextInputElement();
input.spellcheck = false;
input.size = 40;
input.value = att.value;
input.classes.add('invalid');
unknownAttributeFields[att] = input;
h.TableRowElement tr = new h.TableRowElement();
h.TableCellElement td = new h.TableCellElement();
tr.append(td);
td = new h.TableCellElement();
td.appendText(att.name);
tr.append(td);
td = new h.TableCellElement();
td.append(input);
tr.append(td);
table.append(tr);
}
}
form.append(table);
h.DivElement div_buttons = new h.DivElement();
div_buttons.classes.add('buttons');
h.ButtonElement bCancel = new h.ButtonElement();
bCancel.attributes['type'] = 'button';
bCancel.appendText(Strings.get("button.Cancel"));
bCancel.onClick.listen((h.MouseEvent event) => cancel());
div_buttons.append(bCancel);
h.ButtonElement bOk = new h.ButtonElement();
bOk.attributes['type'] = 'submit';
bOk.appendText(Strings.get("button.OK"));
bOk.onClick.listen((h.MouseEvent event) => ok(event));
div_buttons.append(bOk);
form.append(div_buttons);
div3.append(form);
div2.append(div3);
div1.append(div2);
h.document.body.append(div1);
if (toFocus != null)
toFocus.focus();
}
void ok(h.MouseEvent event) {
// check the required attributes
for (x.Element attref in controls.keys) {
SimpleTypeControl control = controls[attref];
String value = control.getValue();
bool required = doc.cfg.requiredAttribute(ref, attref);
if (value == '' && required) {
event.preventDefault();
h.window.alert(Strings.get('attribute.missing_required'));
return;
}
}
// save and close dialog
LinkedHashMap<String, DaxeAttr> attributes = el.getAttributesMapCopy();
for (x.Element attref in controls.keys) {
SimpleTypeControl control = controls[attref];
String name = doc.cfg.attributeQualifiedName(ref, attref);
String value = control.getValue();
String namespace = doc.cfg.attributeNamespace(attref);
String defaultValue = doc.cfg.defaultAttributeValue(attref);
if ((value == '' && defaultValue == null) || value == defaultValue)
attributes.remove(name);
else if (value != '' || defaultValue != null)
attributes[name] = new DaxeAttr.NS(namespace, name, value);
}
if (unknownAttributeFields != null) {
for (DaxeAttr att in unknownAttributeFields.keys) {
h.TextInputElement input = unknownAttributeFields[att];
String name = att.name;
String value = input.value;
String namespace = att.namespaceURI;
if (value == '')
attributes.remove(name);
else
attributes[name] = new DaxeAttr.NS(namespace, name, value);
}
}
h.querySelector('div#attributes_dlg').remove();
event.preventDefault();
List<DaxeAttr> attList = new List.from(attributes.values);
if (el.getHTMLNode() != null) {
UndoableEdit edit = new UndoableEdit.changeAttributes(el, attList);
doc.doNewEdit(edit);
} else {
// this is for a new element
el.attributes = attList;
}
page.focusCursor();
if (okfct != null)
okfct();
}
void cancel() {
h.querySelector('div#attributes_dlg').remove();
page.focusCursor();
}
void help(x.Element attref, x.Element ref) {
HelpDialog dlg = new HelpDialog.Attribute(attref, ref);
dlg.show();
}
}
Index: modules/damieng/graphical_editor/daxe/lib/src/config.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/config.dart
/*
This file is part of Daxe.
Daxe is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Daxe is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Daxe. If not, see <http://www.gnu.org/licenses/>.
*/
part of daxe;
/**
* A Jaxe configuration file. Includes many useful methods to use XML schemas.
*/
class Config {
static final String _typeAffichageParDefaut = "string";
x.Element _cfgroot; // config file root element
String schemaURL; // schema file URL
String _cfgdir; // config folder URL (in which the config file must be)
HashMap<String, x.Element> _elementDisplayCache; // cache for associations nom -> AFFICHAGE_ELEMENT
HashMap<x.Element, String> _elementsToNamesCache; // cache for associations element reference -> name
HashMap<x.Element, String> _elementsTitlesCache; // cache for associations element reference -> title
HashMap<x.Element, Pattern> _insertCache = null; // cache for regular expressions for insertions
HashMap<x.Element, Pattern> _validPatternCache = null;
HashMap<x.Element, HashMap<String, List<String>>> _parametersCache = null;
List<String> _namespaceCache = null; // namespace list
InterfaceSchema _schema; // all the schema management (validity...)
// nodes for the config file main elements
x.Element _languageNode;
x.Element _savingNode;
x.Element _menusNode;
x.Element _displayNode;
x.Element _exportsNode;
List<x.Element> _listeStrings;
// CONSTRUCTORS AND INITIALIZATION
/**
* Constructor (load must be called afterwards)
*/
Config() {
}
/**
* Load a config file
*
* @param cfgFilePath path to the config file
*/
Future load(final String cfgFilePath) { // throws DaxeException
Completer completer = new Completer();
if (cfgFilePath == null) {
_cfgroot = null;
return(new Future.error(new DaxeException("Config.load: null path")));
}
x.DOMParser dp = new x.DOMParser();
dp.parseFromURL(cfgFilePath).then((x.Document configdoc) {
String resource;
if (configdoc.documentElement.nodeName == "CONFIG_JAXE")
resource = null;
else
resource = _getResource(configdoc.documentElement);
_cfgdir = _getParentURL(cfgFilePath);
_cfgroot = configdoc.documentElement;
// AUTRE_CONFIG: ignored
_buildElementDisplayCache();
_elementsTitlesCache = new HashMap<x.Element, String>();
final String noms = schemaName();
if (noms == null) {
final x.Element schema_simple = _findElement(_getLanguage(), "SCHEMA_SIMPLE");
if (schema_simple == null) {
completer.completeError(new DaxeException("Error: no XML schema is defined in the config file $cfgFilePath"));
return;
}
_schema = new SimpleSchema(schema_simple, _titlesHash());
schemaURL = null;
_buildElementsToNamesCache();
completer.complete();
return;
}
if (_cfgdir != null)
schemaURL = "${_cfgdir}/$noms";
else
schemaURL = noms;
_schema = new DaxeWXS(_titlesHash());
(_schema as DaxeWXS).load(schemaURL).then((_) {
_buildElementsToNamesCache();
completer.complete();
}, onError: (WXSException ex) {
completer.completeError(new DaxeException("Error reading schemaURL: $ex"));
});
}, onError: (x.DOMException ex) {
completer.completeError(new DaxeException("Error reading $cfgFilePath: $ex"));
});
return(completer.future);
}
/**
* Returns the URL of the parent of the given URL (file or directory),
* or null if the parent directory cannot be found
*/
static String _getParentURL(final String u) {
final int index = u.lastIndexOf("/");
if (index >= 0) {
return(u.substring(0, index));
}
return(null);
}
// METHODS RELATED TO THE CONFIG FILE
/**
* Returns the name of the first possible root element, or null if none are defined.
*/
String nameOfFirstRootElement() {
final x.Element racine = _findElement(_getLanguage(), "RACINE");
if (racine == null)
return(null);
return(racine.getAttribute("element"));
}
/**
* Returns the reference to the first possible root element, or null if none are defined.
*/
x.Element firstRootElement() {
final String nom = nameOfFirstRootElement();
return(_schema.elementReferenceByName(nom));
}
/**
* Returns the list of names of the possible root elements
*/
List<String> listOfRoots() {
final List<String> liste = new List<String>();
x.Element racine = _findElement(_getLanguage(), "RACINE");
while (racine != null) {
liste.add(racine.getAttribute("element"));
racine = _nextElement(racine, "RACINE");
}
return(liste);
}
/**
* Returns the list of references to the possible root elements
*/
List<x.Element> rootElements() {
// pour éviter une erreur dans le cas d'un schéma définissant un élément global et un élément local
// sous le même nom mais avec des types différents, on est obligé d'aller d'abord chercher les références
// des éléments racines en fonction de l'implémentation du schéma, puis de chercher dedans les éléments
// avec les noms donnés dans la config.
final List<x.Element> liste = new List<x.Element>();
final List<x.Element> racinesPossibles = _schema.rootElements();
x.Element racine = _findElement(_getLanguage(), "RACINE");
while (racine != null) {
final String nom = racine.getAttribute("element");
for (final x.Element ref in racinesPossibles)
if (nom == _schema.elementName(ref))
liste.add(ref);
racine = _nextElement(racine, "RACINE");
}
return(liste);
}
/**
* Adds the attributes for the namespaces to the root node
*/
void addNamespaceAttributes(final DaxeNode root) {
final List<String> espaces = _namespaceList();
for (final String espace in espaces) {
if (espace != "") {
final String prefixe = namespacePrefix(espace);
String nomatt;
if (prefixe != null && prefixe != "")
nomatt = "xmlns:$prefixe";
else
nomatt = "xmlns";
root.setAttributeNS("http://www.w3.org/2000/xmlns/", nomatt, espace);
}
}
final String schemaLocation = getSchemaLocation();
final String noNamespaceSchemaLocation = getNoNamespaceSchemaLocation();
if (schemaLocation != null || noNamespaceSchemaLocation != null) {
root.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
if (schemaLocation != null)
root.setAttributeNS("http://www.w3.org/2001/XMLSchema-instance",
"xsi:schemaLocation", schemaLocation);
if (noNamespaceSchemaLocation != null)
root.setAttributeNS("http://www.w3.org/2001/XMLSchema-instance",
"xsi:noNamespaceSchemaLocation", noNamespaceSchemaLocation);
}
}
/**
* Returns the name of the schema file as given in the config file
* (FICHIER_SCHEMA/@nom)
* Returns null if none is defined
*/
String schemaName() {
final x.Element fichierschema = _findElement(_getLanguage(), "FICHIER_SCHEMA");
if (fichierschema == null)
return(null);
String nom = fichierschema.getAttribute("nom");
if (nom == "")
nom = null;
return(nom);
}
/**
* Returns the hash table by name of the element displays in the config file
* (element AFFICHAGE_ELEMENT)
*/
HashMap<String, x.Element> _buildElementDisplayCache() {
_elementDisplayCache = new HashMap<String, x.Element>();
if (_cfgroot == null)
return(_elementDisplayCache);
x.Element affel = _findElement(_getNodeDisplay(), "AFFICHAGE_ELEMENT");
while (affel != null) {
final String nom = affel.getAttribute("element");
_elementDisplayCache[nom] = affel;
affel = _nextElement(affel, "AFFICHAGE_ELEMENT");
}
return(_elementDisplayCache);
}
HashMap<x.Element, String> _getElementsToNamesCache() {
return(_elementsToNamesCache);
}
x.Element getElementDisplay(String name) {
return _elementDisplayCache[name];
}
/**
* Builds the hash table of associations schema reference -> element name
*/
void _buildElementsToNamesCache() {
_elementsToNamesCache = new HashMap<x.Element, String>();
if (_cfgroot == null)
return;
final List<x.Element> elements = _schema.allElements();
for (final x.Element ref in elements) {
final String nom = _schema.elementName(ref);
if (nom != null)
_elementsToNamesCache[ref] = nom;
}
}
/**
* Return the name of the resource bundle to use.
*
* @return the name of the resource bundle, null if not defined.
*/
String _getResource(final x.Element root) {
final x.Element bundle = _findElement(root, "FICHIERTITRES");
if (bundle == null)
return(null);
return(bundle.getAttribute("nom"));
}
/**
* Returns the list of export references, depending on the output (HTML or XML)
*/
List<x.Element> exportsList(final String output) {
if (_cfgroot == null)
return(null);
final List<x.Element> liste = new List<x.Element>();
x.Element export = _findElement(_getExports(), "EXPORT");
while (export != null) {
if (output == export.getAttribute("sortie"))
liste.add(export);
export = _nextElement(export, "EXPORT");
}
return(liste);
}
/**
* Returns an export name based on its reference
*/
String exportName(final x.Element exportRef) {
return(exportRef.getAttribute("nom"));
}
/**
* Returns the output of an export based on its reference
*/
String exportOutput(final x.Element exportRef) {
return(exportRef.getAttribute("sortie"));
}
/**
* Returns the character encoding to use for new XML documents
*/
String getEncoding() {
final x.Element encodage = _findElement(_getSaving(), "ENCODAGE");
if (encodage == null)
return(null);
return(_dom_elementValue(encodage));
}
String getPublicId() {
final x.Element doctype = _findElement(_getSaving(), "DOCTYPE");
if (doctype != null)
return doctype.getAttribute("publicId");
return(null);
}
String getSystemId() {
final x.Element doctype = _findElement(_getSaving(), "DOCTYPE");
if (doctype != null)
return doctype.getAttribute("systemId");
return(null);
}
String getSchemaLocation() {
final x.Element sl = _findElement(_getSaving(), "SCHEMALOCATION");
if (sl != null) {
final String schemaLocation = sl.getAttribute("schemaLocation");
if (schemaLocation != "")
return(schemaLocation);
}
return(null);
}
String getNoNamespaceSchemaLocation() {
final x.Element sl = _findElement(_getSaving(), "SCHEMALOCATION");
if (sl != null) {
final String noNamespaceSchemaLocation = sl.getAttribute("noNamespaceSchemaLocation");
if (noNamespaceSchemaLocation != "")
return(noNamespaceSchemaLocation);
}
return(null);
}
/**
* Returns a prefix to use for the given namespace, or null if none is found
*/
String namespacePrefix(final String namespace) {
if (namespace == "http://www.w3.org/XML/1998/namespace")
return("xml");
x.Element pe = _findElement(_getSaving(), "PREFIXE_ESPACE");
while (pe != null) {
if (namespace == pe.getAttribute("uri"))
return(pe.getAttribute("prefixe"));
pe = _nextElement(pe, "PREFIXE_ESPACE");
}
return(_schema.namespacePrefix(namespace));
}
// METHODS FOR THE ELEMENT INSERT MENUS
/**
* Returns a menu matching the menu definition in the config file.
*
* @param doc The Daxe document
* @param menudef The MENU element in the config file
*/
Menu _creationMenu(final DaxeDocument doc, final x.Element menudef) {
final String nomMenu = menudef.getAttribute("nom");
String titreM = menuTitle(nomMenu);
final Menu menu = new Menu(titreM);
String docMenu = menuDocumentation(nomMenu);
if (docMenu != null) {
//docMenu = "<html><body>{docMenu.replaceAll('\n', '<br>')}</body></html>";
menu.toolTipText = docMenu;
}
x.Node menunode = menudef.firstChild;
while (menunode != null) {
MenuItem item = null;
final String nodename = menunode.nodeName;
String shortcut = null;
if (menunode is x.Element) {
final String commande = (menunode as x.Element).getAttribute("raccourci");
if (commande != null && commande != "") {
shortcut = commande.toUpperCase()[0];
}
}
if (nodename == "MENU_INSERTION") {
final x.Element insnoeud = menunode as x.Element;
final String nom = insnoeud.getAttribute("nom");
final String titre = menuTitle(nom);
String typeNoeud = insnoeud.getAttribute("type_noeud");
if (typeNoeud == "")
typeNoeud = "element";
x.Element refElement;
if (typeNoeud == "element") {
refElement = elementReference(nom);
if (refElement == null)
logError("Erreur: MENU_INSERTION: pas de référence pour '$nom' dans le schéma");
} else
refElement = null;
item = new MenuItem(titre, () => doc.insertNewNode(refElement, typeNoeud), shortcut: shortcut, data: refElement);
menu.add(item);
String itemdoc = documentation(refElement);
if (itemdoc != null) {
//itemdoc = formatDoc(itemdoc);
item.toolTipText = itemdoc;
}
} else if (nodename == "MENU_FONCTION") {
final x.Element fonction = menunode as x.Element;
final String classe = fonction.getAttribute("classe");
final String nom = fonction.getAttribute("nom");
final String titre = menuTitle(nom);
item = new MenuItem(titre, () => doc.executeFunction(classe, fonction), shortcut: shortcut);
menu.add(item);
String itemdoc = menuDocumentation(nom);
if (itemdoc != null) {
//itemdoc = formatDoc(itemdoc);
item.toolTipText = itemdoc;
}
} else if (nodename == "MENU") {
item = _creationMenu(doc, menunode as x.Element);
menu.add(item);
} else if (nodename == "SEPARATEUR")
menu.addSeparator();
menunode = menunode.nextSibling;
}
return(menu);
}
/**
* Returns a menubar to insert menus.
*
* @param doc The Daxe document
*/
MenuBar makeMenus(final DaxeDocument doc) {
final MenuBar mbar = new MenuBar();
final x.Element menus = _getMenus();
if (menus != null) {
x.Element menudef = _findElement(menus, "MENU");
while (menudef != null) {
final Menu jmenu = _creationMenu(doc, menudef);
jmenu.parent = mbar;
mbar.add(jmenu);
menudef = _nextElement(menudef, "MENU");
}
}
return(mbar);
}
// METHODS RELATED TO THE SCHEMA
InterfaceSchema getSchema() {
return(_schema);
}
/**
* Returns the references for all the elements in the schema
*/
List<x.Element> allElementsList() {
final List<x.Element> liste = _schema.allElements();
return(liste);
}
bool _elementInSchema(final x.Element elementRef) {
return(_schema.elementInSchema(elementRef));
}
/**
* Returns the name of the element
*/
String elementName(final x.Element elementRef) {
return(_elementsToNamesCache[elementRef]);
}
/**
* Returns the reference of the first matching element in the schema,
* based on the element and the reference of its parent
*/
x.Element getElementRef(final x.Element el, final x.Element parentRef) {
return(_schema.elementReference(el, parentRef));
}
/**
* Returns the reference for the first element with the given name
*/
x.Element elementReference(final String name) {
final x.Element el = _schema.elementReferenceByName(localValue(name));
return(el);
}
/**
* Returns the references of the elements with the given name
*/
List<x.Element> elementReferences(final String name) {
return(_schema.elementReferencesByName(localValue(name)));
}
/**
* Returns the namespace to use for the element,
* or null the namespace is undefined.
*/
String elementNamespace(final x.Element elementRef) {
return(_schema.elementNamespace(elementRef));
}
/**
* Returns the prefix to use for a new element with the given reference,
* or null if no prefix should be used.
*/
String elementPrefix(final x.Element elementRef) {
final String espace = elementNamespace(elementRef);
if (espace == null)
return(null);
return(namespacePrefix(espace));
}
/**
* Returns the list of possible values for an element.
* Returns null if there are an infinity of possible values.
*/
List<String> elementValues(final x.Element elementRef) {
final List<String> liste = _schema.elementValues(elementRef);
return(liste);
}
/**
* Returns true if the given value is valid for the element
*/
bool isElementValueValid(final x.Element elementRef, final String value) {
return(_schema.elementValueIsValid(elementRef, value));
}
/**
* Returns the list of all namespaces in the schema
*/
List<String> _namespaceList() {
if (_namespaceCache != null)
return(_namespaceCache);
final List<String> liste = new List<String>();
final List<String> espacesSchema = _schema.namespaceList();
if (espacesSchema != null)
liste.addAll(espacesSchema);
_namespaceCache = liste;
return(liste);
}
/**
* Returns a number for the given namespace, starting from 0.
* A unique number is given for each namespace.
* Returns -1 if the namespace is not found in the config.
*/
int namespaceNumber(final String namespace) {
final List<String> liste = _namespaceList();
return(liste.indexOf(namespace));
}
/**
* Returns true if the namspace is defined in the config
*/
bool hasNamespace(final String namespace) {
return(_schema.hasNamespace(namespace));
}
/**
* Returns the target namespace for the schema (targetNamespace attribute for WXS)
*/
String targetNamespace() {
return(_schema.getTargetNamespace());
}
/**
* Returns the references of the elements which are not in the given namespace
*/
List<x.Element> elementsOutsideNamespace(final String namespace) {
return(_schema.elementsOutsideNamespace(namespace));
}
/**
* Returns the references of the elements which are in the given namespaces
*/
List<x.Element> elementsWithinNamespaces(final Set<String> namespaces) {
return(_schema.elementsWithinNamespaces(namespaces));
}
/**
* Returns true if the child is required under the parent.
*/
bool requiredElement(final x.Element parentRef, final x.Element childRef) {
return(_schema.requiredElement(parentRef, childRef));
}
/**
* Returns true if there is a relation parent-child between the 2 elements
*/
bool isSubElement(final x.Element parentRef, final x.Element childRef) {
final List<x.Element> children = subElements(parentRef);
if (children == null)
return(false);
return(children.contains(childRef));
}
/**
* Returns the first reference in the list that is a child of the parent, or null if none is found.
*/
x.Element findSubElement(final x.Element parentRef, final List<x.Element> refs) {
final List<x.Element> children = subElements(parentRef);
if (children == null)
return(null);
for (x.Element ref in refs)
if (children.contains(ref))
return(ref);
return(null);
}
/**
* Returns true if the given name matches a possible child for the given parent
*/
bool isSubElementByName(final x.Element parentRef, String childName) {
final int inds = childName.indexOf(':');
if (inds != -1)
childName = childName.substring(inds+1);
final List<String> noms = subElementsNames(parentRef);
return(noms.contains(childName));
}
/**
* Returns the references of the given element's children
*/
List<x.Element> subElements(final x.Element parentRef) {
return(_schema.subElements(parentRef));
}
/**
* Returns the names of the given element's children
*/
List<String> subElementsNames(final x.Element parentRef) {
final List<x.Element> listeReferences = subElements(parentRef);
final List<String> listeNoms = new List<String>();
for (final x.Element ref in listeReferences) {
final String nom = _elementsToNamesCache[ref];
if (!listeNoms.contains(nom))
listeNoms.add(nom);
}
return(listeNoms);
}
/**
* Regular expression for a given element
* @param modevisu True to get a regular expression to display to the user
* @param modevalid For strict validation instead of checking if an insert is possible
*/
String _regularExpression(final x.Element parentRef, final bool modevisu, final bool modevalid) {
return(_schema.regularExpression(parentRef, modevisu, modevalid));
}
/**
* Regular expression based on the schema for a given parent element
*/
String regularExpression(final x.Element parentRef) {
return(_schema.regularExpression(parentRef, true, false));
}
/**
* Returns true if the toInsert element can be inserted under the parent element
* on the selection defined by the start and end positions
*/
bool insertIsPossible(DaxeNode parent, final int startOffset, final int endOffset, final x.Element toInsert) {
if (parent.nodeType == DaxeNode.DOCUMENT_NODE) {
for (DaxeNode dn in parent.childNodes) {
if (dn.nodeType == DaxeNode.ELEMENT_NODE)
return(false);
}
return(true);
}
assert(parent.nodeType == DaxeNode.ELEMENT_NODE);
if (_schema is SimpleSchema)
return(true); // on suppose que le test de sous-élément a déjà été fait
if (startOffset < 0) {
logError("Config.insertionPossible: debutSelection < parent.debut");
return(false);
}
if (_schema is DaxeWXS) {
final List<x.Element> sousElements = new List<x.Element>();
bool ajoute = false;
for (DaxeNode dn = parent.firstChild; dn != null; dn = dn.nextSibling) {
if (dn.nodeType == DaxeNode.ELEMENT_NODE) {
int offset = parent.offsetOf(dn);
if (offset < startOffset || offset >= endOffset) {
if (!ajoute && offset >= endOffset) {
sousElements.add(toInsert);
ajoute = true;
}
sousElements.add(dn.ref);
}
}
}
if (!ajoute)
sousElements.add(toInsert);
final bool insertionOK = (_schema as DaxeWXS).validElement(parent.ref, sousElements, true);
return(insertionOK);
}
return(false);
/*
pb: on ne peut pas tester l'ordre des éléments dans certains cas, par exemple:
<html>
<head>
<xsl:if test='truc'>
<title>xxx</title>
</xsl:if>
<xsl:if test='not(truc)'>
<title>yyy</title>
</xsl:if>
</head>
</html>
Ici on autorise deux éléments title sous head alors qu'un seul est normalement autorisé.
Par contre on peut tester les imbrications (title est autorisé sous head).
*/
}
/**
* Returns true if the parent element is valid, considering its attributes,
* its first level children, its node value, and its parent if there is one.
*/
bool elementIsValid(final DaxeNode parent) {
if (parent is DNComment || parent is DNProcessingInstruction || parent is DNCData)
return(true);
if (parent.ref == null)
return(false);
if (!attributesAreValid(parent))
return(false);
if (parent.parent != null && parent.parent.ref != null && !isSubElement(parent.parent.ref, parent.ref))
return(false);
if (parent.firstChild == null && !isElementValueValid(parent.ref, ''))
return(false);
else if (parent.childNodes.length == 1 && parent.firstChild is DNText && parent.firstChild.nodeValue != null &&
!isElementValueValid(parent.ref, parent.firstChild.nodeValue))
return(false);
if (_schema is SimpleSchema)
return(true); // on suppose que le test de sous-balise a déjà été fait
if (_schema is DaxeWXS) {
final List<x.Element> sousElements = new List<x.Element>();
bool avectexte = false;
for (DaxeNode dn = parent.firstChild; dn != null; dn = dn.nextSibling) {
if (dn.nodeType == DaxeNode.ELEMENT_NODE && dn.ref != null) {
sousElements.add(dn.ref);
} else if (dn.nodeType == DaxeNode.TEXT_NODE) {
if (dn.nodeValue.trim() != "")
avectexte = true;
} else if (dn is DNCData) {
if (dn.firstChild != null && dn.firstChild.nodeValue.trim() != '')
avectexte = true;
}
}
if (avectexte && !_schema.canContainText(parent.ref))
return(false);
final DaxeWXS sch = _schema as DaxeWXS;
return(sch.validElement(parent.ref, sousElements, false));
}
final x.Element refParent = parent.ref;
final StringBuffer cettexp = new StringBuffer();
if (_validPatternCache == null)
_validPatternCache = new HashMap<x.Element, Pattern>();
bool avectexte = false;
DaxeNode child = parent.firstChild;
while (child != null) {
if (child is DNCData) {
if (child.firstChild != null && child.firstChild.nodeValue.trim() != '')
avectexte = true;
} else if (child.nodeType == DaxeNode.ELEMENT_NODE && child is! DNComment && child is! DNProcessingInstruction) {
cettexp.write(child.localName);
cettexp.write(",");
} else if (child.nodeType == DaxeNode.TEXT_NODE) {
if (child.nodeValue.trim() != '')
avectexte = true;
}
child = child.nextSibling;
}
if (avectexte && !_schema.canContainText(refParent))
return(false);
RegExp r = _validPatternCache[refParent];
if (r == null) {
final String expr = _regularExpression(refParent, false, true);
if (expr == null || expr == "")
return(true);
try {
r = new RegExp(r"^$expr$");
} on Exception catch(ex) {
logError("elementValide(JaxeElement, bool, List<String>) - Malformed Pattern: ^${expr}\$:", ex);
return(true);
}
_validPatternCache[refParent] = r;
}
final bool matched = r.hasMatch(cettexp.toString());
return(matched);
}
/**
* Returns true if the element attributes are valid and if there is not missing required attribute.
*/
bool attributesAreValid(final DaxeNode dn) {
if (dn.nodeType != DaxeNode.ELEMENT_NODE) {
logError("Config.attributsValides : ce n'est pas un élément: $dn");
return(false);
}
// vérif des attributs qui sont dans le schéma
final List<x.Element> lattref = elementAttributes(dn.ref);
List<String> noms = new List<String>(lattref.length);
List<String> espaces = new List<String>(noms.length);
for (int i=0; i<lattref.length; i++) {
final x.Element attref = lattref[i];
noms[i] = attributeName(attref);
espaces[i] = attributeNamespace(attref);
final String valeur = dn.getAttribute(noms[i]);
if (valeur == null || valeur == '') {
if (requiredAttribute(dn.ref, attref))
return(false);
} else if (!validAttributeValue(attref, valeur))
return(false);
}
// vérif s'il y a des attributs en plus qui ne sont pas dans le schéma
final List<DaxeAttr> latt = dn.attributes;
for (int i=0; i<latt.length; i++) {
DaxeAttr att = latt[i];
final String prefixe = att.prefix;
if (prefixe == "xml" || prefixe == "xmlns")
continue;
final String nom = att.localName;
if (prefixe == null && nom == "xmlns")
continue;
final String espace = att.namespaceURI;
if (espace == "http://www.w3.org/2001/XMLSchema-instance")
continue;
bool trouve = false;
for (int j=0; j<noms.length; j++) {
if (noms[j] == nom && espaces[j] == espace) {
trouve = true;
break;
}
}
if (!trouve)
return(false);
}
return(true);
}
/**
* Returns the list of possible parent elements for a given element
*/
List<x.Element> parentElements(final x.Element elementRef) {
return(_schema.parentElements(elementRef));
}
/**
* Returns the list of names for possible parent elements for a given element
*/
List<String> parentNames(final x.Element elementRef) {
final List<x.Element> listeReferences = parentElements(elementRef);
final List<String> listeNoms = new List<String>();
for (final x.Element ref in listeReferences) {
final String nom = _elementsToNamesCache[ref];
if (!listeNoms.contains(nom))
listeNoms.add(nom);
}
return(listeNoms);
}
/**
* Returns true if the given element can contain text
*/
bool canContainText(final x.Element elementRef) {
if (elementRef == null)
return(true);
return(_schema.canContainText(elementRef));
}
/**
* Returns the list of possible attributes for a given element.
*/
List<x.Element> elementAttributes(final x.Element elementRef) {
return(_schema.elementAttributes(elementRef));
}
/**
* Returns the name of an attribute based on its reference.
*/
String attributeName(final x.Element attributeRef) {
return(_schema.attributeName(attributeRef));
}
/**
* Returns the qualified name of an attribute based on its reference.
*/
String attributeQualifiedName(final x.Element parentRef, final x.Element attributeRef) {
String name = _schema.attributeName(attributeRef);
String namespace = _schema.attributeNamespace(attributeRef);
if (namespace != null) {
String prefix = attributePrefix(parentRef, attributeRef);
if (prefix != null)
name = "$prefix:$name";
}
return(name);
}
/**
* Returns an attribute namespace based on its reference, or null if none is defined.
*/
String attributeNamespace(final x.Element attributeRef) {
return(_schema.attributeNamespace(attributeRef));
}
/**
* Returns the prefix tu use to create an attribute, given the parent element and the attribute reference,
* or null if no prefix should be used.
*/
String attributePrefix(final x.Element parent, final x.Element attributeRef) {
final String espace = attributeNamespace(attributeRef);
if (espace == null)
return(null);
if (espace == "http://www.w3.org/XML/1998/namespace")
return("xml");
if (espace == "http://www.w3.org/2000/xmlns/" && attributeName(attributeRef) != "xmlns")
return("xmlns");
// on essaye lookupPrefix avec le parent et avec son document
// (cas d'un élément en cours de création, pas encore inséré dans le document)
String prefixe = parent.lookupPrefix(espace);
if (prefixe == null) {
if (parent.ownerDocument.documentElement != null) // si l'élément racine existe
prefixe = parent.ownerDocument.lookupPrefix(espace);
else
prefixe = namespacePrefix(espace); // on suppose que la racine sera créée avec ajouterAttributsEspaces
}
return(prefixe);
}
/**
* Returns an attribute namespace based on its full name (including the prefix).
*/
String attributeNamespaceByName(final String name) {
return(_schema.attributeNamespaceByName(name));
}
/**
* Returns true if the attribute is required for the parent element.
*/
bool requiredAttribute(final x.Element parentRef, final x.Element attributeRef) {
return(_schema.attributeIsRequired(parentRef, attributeRef));
}
/**
* Returns the list of possible values for an attribute.
* Returns null if there are an infinity of possible values.
*/
List<String> attributeValues(final x.Element attributeRef) {
final List<String> liste = _schema.attributeValues(attributeRef);
return(liste);
}
/**
* Returns an attribute's default value based on its reference.
*/
String defaultAttributeValue(final x.Element attributeRef) {
return(_schema.defaultAttributeValue(attributeRef));
}
/**
* Returns true if the given String is a valid value for the attribute.
*/
bool validAttributeValue(final x.Element attributeRef, final String value) {
return(_schema.attributeIsValid(attributeRef, value));
}
/**
* Returns the local part of an element's name (by removing the prefix).
*/
static String localValue(final String s) {
if (s == null)
return(null);
final int ind = s.indexOf(':');
if (ind == -1)
return(s);
return(s.substring(ind + 1));
}
// METHODS FOR DISPLAY TYPES
/**
* Returns a node display type based on the element reference, the node name and the DOM node type.
*/
String nodeDisplayType(final x.Element elementRef, final String name, final int nodeType) {
if (nodeType == x.Node.ELEMENT_NODE) {
final x.Element affel = getElementDisplay(localValue(name));
if (affel == null)
return(_typeAffichageParDefaut);
return(affel.getAttribute("type"));
} else if (nodeType == x.Node.PROCESSING_INSTRUCTION_NODE) {
x.Element elplug = _findElement(_getNodeDisplay(), "PLUGIN_INSTRUCTION");
while (elplug != null) {
if (name != null && name == elplug.getAttribute("cible"))
return("plugin");
elplug = _nextElement(elplug, "PLUGIN_INSTRUCTION");
}
return("instruction");
} else if (nodeType == x.Node.COMMENT_NODE) {
final x.Element elplug = _findElement(_getNodeDisplay(), "PLUGIN_COMMENTAIRE");
if (elplug != null)
return("plugin");
return("commentaire");
} else if (nodeType == x.Node.CDATA_SECTION_NODE) {
final x.Element elplug = _findElement(_getNodeDisplay(), "PLUGIN_CDATA");
if (elplug != null)
return("plugin");
return("cdata");
} else if (nodeType == x.Node.TEXT_NODE) {
return("texte");
}
return(null);
}
/**
* Returns an element display type based on its reference.
*/
String elementDisplayType(final x.Element elementRef) {
final x.Element affel = getElementDisplay(elementName(elementRef));
if (affel == null)
return(_typeAffichageParDefaut);
return(affel.getAttribute("type"));
}
/**
* Returns the reference of the first element with the given display type in the config file.
*/
x.Element firstElementWithType(final String displayType) {
if (_cfgroot == null)
return(null);
x.Element affel = _findElement(_getNodeDisplay(), "AFFICHAGE_ELEMENT");
while (affel != null) {
if (displayType == affel.getAttribute("type"))
return(elementReference(affel.getAttribute("element")));
affel = _nextElement(affel, "AFFICHAGE_ELEMENT");
}
return(null);
}
/**
* Returns the references of the elements with the given display type in the config file.
*/
List<x.Element> elementsWithType(final String displayType) {
if (_cfgroot == null)
return(null);
List<x.Element> list = new List<x.Element>();
x.Element affel = _findElement(_getNodeDisplay(), "AFFICHAGE_ELEMENT");
while (affel != null) {
if (displayType == affel.getAttribute("type"))
list.addAll(elementReferences(affel.getAttribute("element")));
affel = _nextElement(affel, "AFFICHAGE_ELEMENT");
}
return(list);
}
/**
* Returns the value of an element display parameter.
* @param elementRef element reference
* @param parameterName parameter name
* @param defaultValue default value, used if the parameter is not found
*/
String elementParameterValue(final x.Element elementRef, final String parameterName, final String defaultValue) {
return nodeParameterValue(elementRef, "element", null, parameterName, defaultValue);
}
/**
* Returns the value of a node display parameter.
* The node type can be used to find display parameters for comments or PIs.
*/
String nodeParameterValue(final x.Element elementRef, final String nodeType,
final String name, final String parameterName, final String defaultValue) {
final HashMap<String, List<String>> table = getNodeParameters(elementRef, nodeType, name);
final List<String> lval = table[parameterName];
String valeur;
if (lval != null && lval.length > 0)
valeur = lval[0];
else
valeur = defaultValue;
return valeur;
}
/**
* Returns a function parameter value.
* @param fctdef Element for the function menu in the config file
* @param parameterName parameter name
* @param defaultValue default value, used if the parameter is not found
*/
String functionParameterValue(final x.Element fctdef, final String parameterName, final String defaultValue) {
x.Element parel = _findElement(fctdef, "PARAMETRE");
while (parel != null) {
final String nom = parel.getAttribute("nom");
if (nom == parameterName)
return(parel.getAttribute("valeur"));
parel = _nextElement(parel, "PARAMETRE");
}
return(defaultValue);
}
HashMap<String, List<String>> _buildParameterCache(final x.Element base) {
final HashMap<String, List<String>> hashparams = new HashMap<String, List<String>>();
x.Element parel = _findElement(base, "PARAMETRE");
while (parel != null) {
final String nom = parel.getAttribute("nom");
final String valeur = parel.getAttribute("valeur");
List<String> lval = hashparams[nom];
if (lval == null) {
lval = new List<String>();
lval.add(valeur);
hashparams[nom] = lval;
} else
lval.add(valeur);
parel = _nextElement(parel, "PARAMETRE");
}
_parametersCache[base] = hashparams;
return(hashparams);
}
/**
* Returns the table of an element display parameters.
*/
HashMap<String, List<String>> getElementParameters(final x.Element elementRef) {
return(getNodeParameters(elementRef, "element", null));
}
/**
* Returns the table of a node display parameters.
* The name can be null if nodeType is "element" and elementRef is not null.
*/
HashMap<String, List<String>> getNodeParameters(final x.Element elementRef, final String nodeType, final String name) {
x.Element base;
if (nodeType == "element")
base = getElementDisplay(elementName(elementRef));
else if (nodeType == "instruction") {
base = null;
x.Element elplug = _findElement(_getNodeDisplay(), "PLUGIN_INSTRUCTION");
while (elplug != null) {
if (name != null && name == elplug.getAttribute("cible")) {
base = elplug;
break;
}
elplug = _nextElement(elplug, "PLUGIN_INSTRUCTION");
}
} else if (nodeType == "commentaire") {
final x.Element elplug = _findElement(_getNodeDisplay(), "PLUGIN_COMMENTAIRE");
if (elplug == null) {
base = null;
} else {
base = elplug;
}
} else
base = null;
if (base == null)
return(new HashMap<String, List<String>>());
if (_parametersCache == null)
_parametersCache = new HashMap<x.Element, HashMap<String, List<String>>>();
HashMap<String, List<String>> hashparams = _parametersCache[base];
if (hashparams == null)
hashparams = _buildParameterCache(base);
return(hashparams);
}
/**
* Returns the list of suggested values for a given element.
* Returns null if there is no suggestion.
*/
List<String> elementSuggestedValues(final x.Element elementRef) {
final Set<String> set = new LinkedHashSet<String>();
List<String> schemaSuggestions = _schema.suggestedElementValues(elementRef);
if (schemaSuggestions != null)
set.addAll(_schema.suggestedElementValues(elementRef));
final x.Element affel = getElementDisplay(elementName(elementRef));
if (affel != null) {
x.Element vs = _findElement(affel, "VALEUR_SUGGEREE");
while (vs != null) {
final String v = _dom_elementValue(vs);
if (v != null)
set.add(v);
vs = _nextElement(vs, "VALEUR_SUGGEREE");
}
}
if (set.length == 0)
return(null);
else
return(set.toList());
}
/**
* Returns the list of suggested values for an attribute,
* based on the parent element reference and the attribute reference.
* Returns null if there is no suggestion.
*/
List<String> attributeSuggestedValues(final x.Element parentRef, final x.Element attributeRef) {
final Set<String> set = new LinkedHashSet<String>();
List<String> schemaSuggestions = _schema.suggestedAttributeValues(attributeRef);
if (schemaSuggestions != null)
set.addAll(schemaSuggestions);
final x.Element affel = getElementDisplay(elementName(parentRef));
if (affel != null) {
final String nomAtt = attributeName(attributeRef);
x.Element aa = _findElement(affel, "AFFICHAGE_ATTRIBUT");
while (aa != null) {
if (aa.getAttribute("attribut") == nomAtt) {
x.Element vs = _findElement(aa, "VALEUR_SUGGEREE");
while (vs != null) {
final String v = _dom_elementValue(vs);
if (v != null)
set.add(v);
vs = _nextElement(vs, "VALEUR_SUGGEREE");
}
}
aa = _nextElement(aa, "AFFICHAGE_ATTRIBUT");
}
}
if (set.length == 0)
return(null);
else
return(set.toList());
}
// METHODS FOR THE STRINGS
/**
* Returns a list of the STRINGS elements in the config file,
* ordered by preference based on the user language and country.
*/
List<x.Element> _stringsElements() {
final Locale defaut = new Locale();
final List<x.Element> liste = new List<x.Element>();
final List<x.Element> lstrings = _getStrings();
for (final x.Element strings in lstrings) {
final String langue = strings.getAttribute("langue");
if (langue != "") {
Locale strloc;
if (strings.getAttribute("pays") == "")
strloc = new Locale.l(langue);
else
strloc = new Locale.lc(langue, strings.getAttribute("pays"));
if (defaut == strloc && !liste.contains(strings))
liste.add(strings);
}
}
for (final x.Element strings in lstrings) {
final String langue = strings.getAttribute("langue");
if (langue != "") {
final Locale test = new Locale.lc(defaut.language, defaut.country);
Locale strloc;
if (strings.getAttribute("pays") == "")
strloc = new Locale.l(langue);
else
strloc = new Locale.lc(langue, strings.getAttribute("pays"));
if (test == strloc && !liste.contains(strings))
liste.add(strings);
}
}
for (final x.Element strings in lstrings) {
final String langue = strings.getAttribute("langue");
if (langue != "") {
final Locale test = new Locale.l(defaut.language);
if (test == new Locale.l(langue) && !liste.contains(strings))
liste.add(strings);
}
}
for (final x.Element strings in lstrings) {
if (!liste.contains(strings))
liste.add(strings);
}
return(liste);
}
/**
* Returns the config description (element DESCRIPTION_CONFIG).
*/
String description() {
final List<x.Element> lstrings = _stringsElements();
for (final x.Element strings in lstrings) {
final x.Element descel = _findElement(strings, "DESCRIPTION_CONFIG");
if (descel == null || descel.firstChild == null)
break;
String desc = _dom_elementValue(descel);
return(desc);
}
return(null);
}
/**
* Returns a menu title based on its name
*/
String menuTitle(final String name) {
final List<x.Element> lstrings = _stringsElements();
for (final x.Element strings in lstrings) {
x.Element sm = _findElementDeep(strings, "STRINGS_MENU");
while (sm != null) {
if (name == sm.getAttribute("menu")) {
final x.Element eltitre = _findElement(sm, "TITRE");
if (eltitre != null && eltitre.firstChild != null) {
return(_dom_elementValue(eltitre));
}
break;
}
sm = _nextElementDeep(strings, sm, "STRINGS_MENU");
}
}
final x.Element refel = elementReference(name);
if (refel != null)
return(elementTitle(refel));
return(name);
}
/**
* Returns a menu documentation based on its name.
*/
String menuDocumentation(final String name) {
final List<x.Element> lstrings = _stringsElements();
for (final x.Element strings in lstrings) {
x.Element sm = _findElementDeep(strings, "STRINGS_MENU");
while (sm != null) {
if (name == sm.getAttribute("menu")) {
final x.Element eldoc = _findElement(sm, "DOCUMENTATION");
if (eldoc != null && eldoc.firstChild != null) {
return(_dom_elementValue(eldoc));
}
break;
}
sm = _nextElementDeep(strings, sm, "STRINGS_MENU");
}
}
return(null);
}
HashMap<String, String> _titlesHash() {
HashMap<String, String> h = new HashMap<String, String>();
final List<x.Element> lstrings = _stringsElements();
for (final x.Element strings in lstrings) {
x.Element sel = _findElement(strings, "STRINGS_ELEMENT");
while (sel != null) {
String nom = sel.getAttribute("element");
if (h[nom] == null) {
String titre = nom;
final x.Element eltitre = _findElement(sel, "TITRE");
if (eltitre != null && eltitre.firstChild != null)
titre = _dom_elementValue(eltitre);
h[nom] = titre;
}
sel = _nextElement(sel, "STRINGS_ELEMENT");
}
}
return(h);
}
/**
* Returns an element title based on its reference.
*/
String elementTitle(final x.Element elementRef) {
String titre = null;
titre = _elementsTitlesCache[elementRef];
if (titre != null)
return(titre);
final String nom = elementName(elementRef);
if (nom == null) {
logError("Config.elementTitle : no name for $elementRef");
return(null);
}
final List<x.Element> lstrings = _stringsElements();
for (final x.Element strings in lstrings) {
if (titre == null) {
x.Element sel = _findElement(strings, "STRINGS_ELEMENT");
while (sel != null) {
if (sel.getAttribute("element") == nom) {
final x.Element eltitre = _findElement(sel, "TITRE");
if (eltitre != null && eltitre.firstChild != null) {
titre = _dom_elementValue(eltitre);
break;
}
break;
}
sel = _nextElement(sel, "STRINGS_ELEMENT");
}
}
}
if (titre == null || titre == "")
titre = nom;
_elementsTitlesCache[elementRef] = titre;
return(titre);
}
/**
* Returns an element documentation
*/
String documentation(final x.Element elementRef) {
if (elementRef == null)
return(null);
final String nom = elementName(elementRef);
final List<x.Element> lstrings = _stringsElements();
for (final x.Element strings in lstrings) {
x.Element sel = _findElement(strings, "STRINGS_ELEMENT");
while (sel != null) {
if (nom == sel.getAttribute("element")) {
final x.Element eldoc = _findElement(sel, "DOCUMENTATION");
if (eldoc != null && eldoc.firstChild != null)
return(_dom_elementValue(eldoc));
break;
}
sel = _nextElement(sel, "STRINGS_ELEMENT");
}
}
return(_schema.elementDocumentation(elementRef));
}
/**
* Formats the documentation in HTML.
*/
static String formatDoc(final String documentation) {
String doc = documentation;
doc = doc.replaceAll("&", "&");
doc = doc.replaceAll("<", "<");
doc = doc.replaceAll(">", ">");
/*
if (doc.length > 100) {
int p = 0;
for (int i=0; i<doc.length; i++) {
if (i-p > 90 && doc[i] == ' ') {
doc = "${doc.substring(0, i)}\n${doc.substring(i+1)}";
p = i;
} else if (doc[i] == '\n')
p = i;
}
}
*/
doc = doc.replaceAll("\n", "<br>");
return(doc);
}
/**
* Returns the title for an element value, based on the element reference and the value.
*/
String elementValueTitle(final x.Element elementRef, final String value) {
final String nom = elementName(elementRef);
final List<x.Element> lstrings = _stringsElements();
final String langueSyst = (new Locale()).language;
for (final x.Element strings in lstrings) {
x.Element sel = _findElement(strings, "STRINGS_ELEMENT");
while (sel != null) {
if (sel.getAttribute("element") == nom) {
x.Element eltitrev = _findElement(sel, "TITRE_VALEUR");
while (eltitrev != null) {
if (eltitrev.getAttribute("valeur") == value &&
eltitrev.firstChild != null)
return(_dom_elementValue(eltitrev));
eltitrev = _nextElement(eltitrev, "TITRE_VALEUR");
}
break;
}
sel = _nextElement(sel, "STRINGS_ELEMENT");
}
// la langue est trouvée mais il n'y a pas de TITRE_VALEUR correspondant
// -> on renvoie la vraie valeur plutôt que de chercher un titre
// dans d'autres langues.
final String langue = strings.getAttribute("langue");
if (langue == langueSyst)
return(value);
}
return(value);
}
/**
* Returns an attribute title based on the parent element reference and the attribute reference.
*/
String attributeTitle(final x.Element parentRef, final x.Element attributeRef) {
final String nomEl = elementName(parentRef);
final String nomAtt = attributeName(attributeRef);
final List<x.Element> lstrings = _stringsElements();
for (final x.Element strings in lstrings) {
x.Element sel = _findElement(strings, "STRINGS_ELEMENT");
while (sel != null) {
if (sel.getAttribute("element") == nomEl) {
x.Element sat = _findElement(sel, "STRINGS_ATTRIBUT");
while (sat != null) {
if (sat.getAttribute("attribut") == nomAtt) {
final x.Element eltitre = _findElement(sat, "TITRE");
if (eltitre != null && eltitre.firstChild != null)
return(_dom_elementValue(eltitre));
break;
}
sat = _nextElement(sat, "STRINGS_ATTRIBUT");
}
}
sel = _nextElement(sel, "STRINGS_ELEMENT");
}
}
final String prefixe = attributePrefix(parentRef, attributeRef);
if (prefixe != null)
return("$prefixe:$nomAtt");
return(nomAtt);
}
/**
* Returns the title for an attribute value, based on the parent element reference,
* the attribute reference and the value.
*/
String attributeValueTitle(final x.Element parentRef, final x.Element attributeRef, final String value) {
final String nomEl = elementName(parentRef);
final String nomAtt = attributeName(attributeRef);
final List<x.Element> lstrings = _stringsElements();
final String langueSyst = (new Locale()).language;
for (final x.Element strings in lstrings) {
x.Element sel = _findElement(strings, "STRINGS_ELEMENT");
while (sel != null) {
if (sel.getAttribute("element") == nomEl) {
x.Element sat = _findElement(sel, "STRINGS_ATTRIBUT");
while (sat != null) {
if (sat.getAttribute("attribut") == nomAtt) {
x.Element eltitrev = _findElement(sat, "TITRE_VALEUR");
while (eltitrev != null) {
if (eltitrev.getAttribute("valeur") == value &&
eltitrev.firstChild != null)
return(_dom_elementValue(eltitrev));
eltitrev = _nextElement(eltitrev, "TITRE_VALEUR");
}
break;
}
sat = _nextElement(sat, "STRINGS_ATTRIBUT");
}
}
sel = _nextElement(sel, "STRINGS_ELEMENT");
}
// la langue est trouvée mais il n'y a pas de TITRE_VALEUR correspondant
// -> on renvoie la vraie valeur d'attribut plutôt que de chercher un titre
// dans d'autres langues.
final String langue = strings.getAttribute("langue");
if (langue == langueSyst)
return(value);
}
return(value);
}
/**
* Returns an attribute's documentation based on the parent element reference and
* the attribute reference.
*/
String attributeDocumentation(final x.Element parentRef, final x.Element attributeRef) {
final String nomEl = elementName(parentRef);
final String nomAtt = attributeName(attributeRef);
final List<x.Element> lstrings = _stringsElements();
for (final x.Element strings in lstrings) {
x.Element sel = _findElement(strings, "STRINGS_ELEMENT");
while (sel != null) {
if (sel.getAttribute("element") == nomEl) {
x.Element sat = _findElement(sel, "STRINGS_ATTRIBUT");
while (sat != null) {
if (sat.getAttribute("attribut") == nomAtt) {
final x.Element eldoc = _findElement(sat, "DOCUMENTATION");
if (eldoc != null &&eldoc.firstChild != null)
return(_dom_elementValue(eldoc));
break;
}
sat = _nextElement(sat, "STRINGS_ATTRIBUT");
}
}
sel = _nextElement(sel, "STRINGS_ELEMENT");
}
}
return(_schema.attributeDocumentation(attributeRef));
}
/**
* Returns an export's title based on its reference.
*/
String exportTitle(final x.Element exportRef) {
final String nom = exportName(exportRef);
final List<x.Element> lstrings = _stringsElements();
for (final x.Element strings in lstrings) {
x.Element export = _findElement(strings, "STRINGS_EXPORT");
while (export != null) {
if (nom == export.getAttribute("export")) {
final x.Element eltitre = _findElement(export, "TITRE");
if (eltitre != null && eltitre.firstChild != null)
return(_dom_elementValue(eltitre));
break;
}
export = _nextElement(export, "STRINGS_EXPORT");
}
}
return(nom);
}
/**
* Returns an export's documentation based on its reference.
*/
String exportDocumentation(final x.Element exportRef) {
final String nom = exportName(exportRef);
final List<x.Element> lstrings = _stringsElements();
for (final x.Element strings in lstrings) {
x.Element export = _findElement(strings, "STRINGS_EXPORT");
while (export != null) {
if (nom == export.getAttribute("export")) {
final x.Element eldoc = _findElement(export, "DOCUMENTATION");
if (eldoc != null && eldoc.firstChild != null)
return(_dom_elementValue(eldoc));
break;
}
export = _nextElement(export, "STRINGS_EXPORT");
}
}
return(null);
}
// TOOLS
/**
* Returns the value of the first child node, removing leading and trailing whites.
* Returns null if there is not child node.
*/
static String _dom_elementValue(final x.Node el) {
final x.Node fc = el.firstChild;
if (fc == null)
return(null);
final String v = fc.nodeValue;
if (v == null)
return(null);
return(v.trim());
}
x.Element _getLanguage() {
if (_languageNode == null) {
_languageNode = _findElement(_cfgroot, "LANGAGE");
}
return _languageNode;
}
x.Element _getSaving() {
if (_savingNode == null) {
_savingNode = _findElement(_cfgroot, "ENREGISTREMENT");
if (_savingNode == null) {
_savingNode = _cfgroot.ownerDocument.createElement("ENREGISTREMENT");
}
}
return _savingNode;
}
x.Element _getMenus() {
if (_menusNode == null) {
_menusNode = _findElement(_cfgroot, "MENUS");
if (_menusNode == null) {
_menusNode = _cfgroot.ownerDocument.createElement("MENUS");
}
}
return _menusNode;
}
x.Element _getNodeDisplay() {
if (_displayNode == null) {
_displayNode = _findElement(_cfgroot, "AFFICHAGE_NOEUDS");
if (_displayNode == null) {
_displayNode = _cfgroot.ownerDocument.createElement("AFFICHAGE_NOEUDS");
}
}
return _displayNode;
}
x.Element _getExports() {
if (_exportsNode == null) {
_exportsNode = _findElement(_cfgroot, "EXPORTS");
if (_exportsNode == null) {
_exportsNode = _cfgroot.ownerDocument.createElement("EXPORTS");
}
}
return _exportsNode;
}
List<x.Element> _getStrings() {
if (_listeStrings == null) {
_listeStrings = new List<x.Element>();
x.Node child = _cfgroot.firstChild;
while (child != null) {
if (child.nodeType == x.Node.ELEMENT_NODE && child.nodeName == "STRINGS") {
_listeStrings.add(child as x.Element);
}
child = child.nextSibling;
}
}
return _listeStrings;
}
static x.Element _findElement(final x.Node n, final String name) {
final x.Node child = n.firstChild;
return _nextNode(child, name);
}
static x.Element _nextElement(final x.Node n, final String name) {
final x.Node child = n.nextSibling;
return _nextNode(child, name);
}
static x.Element _nextNode(x.Node child, final String name) {
if (name == null)
return null;
while (child != null) {
if (child.nodeType == x.Node.ELEMENT_NODE && name == child.nodeName) {
return child as x.Element;
}
child = child.nextSibling;
}
return null;
}
static x.Element _findElementDeep(final x.Node n, final String name) {
return _nextElementDeep(n, n, name);
}
static x.Element _nextElementDeep(final x.Node parent, final x.Node n, final String name) {
x.Node current = n;
x.Node next;
while (current != null) {
if (current.hasChildNodes()) {
current = (current.firstChild);
} else if (current != parent && null != (next = current.nextSibling)) {
current = next;
} else {
next = null;
while (current != parent) {
next = current.nextSibling;
if (next != null)
break;
current = current.parentNode;
}
current = next;
}
if (current != parent && current != null && current.nodeType == x.Node.ELEMENT_NODE
&& current.nodeName == name) {
return current as x.Element;
}
}
return null;
}
static void logError(String message, [Exception ex]) {
if (ex != null)
print("Config: $message: $ex");
else
print("Config: $message");
}
}
Index: modules/damieng/graphical_editor/daxe/lib/src/css_map.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/css_map.dart
/*
This file is part of Daxe.
Daxe is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Daxe is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Daxe. If not, see <http://www.gnu.org/licenses/>.
*/
part of daxe;
class CSSMap extends MapBase<String, String> {
LinkedHashMap<String, String> map;
CSSMap(String css) {
map = new LinkedHashMap<String, String>();
if (css != null) {
for (String cssEntry in css.split(';')) {
List<String> nameValue = cssEntry.split(':');
if (nameValue.length == 2) {
String name = nameValue[0].trim().toLowerCase();
String value = nameValue[1].trim().toLowerCase();
if (name != '' && value != '')
map[name] = value;
}
}
}
}
String operator [](String key) => map[key];
void operator []=(String key, String value) {
map[key] = value;
}
String remove(String key) {
return(map.remove(key));
}
Iterable<String> get keys {
return(map.keys);
}
void clear() {
map.clear;
}
String toString() {
List<String> cssArray = new List<String>();
map.forEach((String key, String value) => cssArray.add(key + ': ' + value));
return(cssArray.join('; '));
}
bool equivalent(CSSMap cssMap) {
List<String> keys1 = keys;
List<String> keys2 = cssMap.map.keys;
return(keys1.every((String key1) => map[key1] == cssMap.map[key1]) &&
keys2.every((String key2) => map[key2] == cssMap.map[key2]));
}
}
Index: modules/damieng/graphical_editor/daxe/lib/src/cursor.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/cursor.dart
/*
This file is part of Daxe.
Daxe is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Daxe is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Daxe. If not, see <http://www.gnu.org/licenses/>.
*/
part of daxe;
/**
* Cursor and related operations (such as keyboard input)
*/
class Cursor {
h.TextAreaElement ta;
h.SpanElement caret;
Position selectionStart, selectionEnd;
List<h.SpanElement> spansSelection = new List<h.SpanElement>();
List<DaxeNode> selectedNodes = new List<DaxeNode>();
bool visible;
static const Duration delay = const Duration(milliseconds: 700);
Timer timer;
HashMap<int, ActionFunction> shortcuts;
Cursor() {
ta = h.querySelector("#tacursor");
caret = h.querySelector("#caret");
visible = true;
shortcuts = new HashMap<int, ActionFunction>();
// FIXME: IE is always intercepting Ctrl-P
ta.onKeyUp.listen((h.KeyboardEvent event) => keyUp(event));
ta.onKeyDown.listen((h.KeyboardEvent event) => keyDown(event));
ta.onBlur.listen((h.Event event) => blur(event));
newTimer();
}
void setShortcuts(HashMap<String, ActionFunction> stringShortcuts) {
HashMap<String, int> mappings = new HashMap<String, int>();
mappings['A'] = h.KeyCode.A;
mappings['B'] = h.KeyCode.B;
mappings['C'] = h.KeyCode.C;
mappings['D'] = h.KeyCode.D;
mappings['E'] = h.KeyCode.E;
mappings['F'] = h.KeyCode.F;
mappings['G'] = h.KeyCode.G;
mappings['H'] = h.KeyCode.H;
mappings['I'] = h.KeyCode.I;
mappings['J'] = h.KeyCode.J;
mappings['K'] = h.KeyCode.K;
mappings['L'] = h.KeyCode.L;
mappings['M'] = h.KeyCode.M;
mappings['N'] = h.KeyCode.N;
mappings['O'] = h.KeyCode.O;
mappings['P'] = h.KeyCode.P;
mappings['Q'] = h.KeyCode.Q;
mappings['R'] = h.KeyCode.R;
mappings['S'] = h.KeyCode.S;
mappings['T'] = h.KeyCode.T;
mappings['U'] = h.KeyCode.U;
mappings['V'] = h.KeyCode.V;
mappings['W'] = h.KeyCode.W;
mappings['X'] = h.KeyCode.X;
mappings['Y'] = h.KeyCode.Y;
mappings['Z'] = h.KeyCode.Z;
for (String key in stringShortcuts.keys) {
String up = key.toUpperCase();
if (mappings[up] != null)
shortcuts[mappings[up]] = stringShortcuts[key];
}
}
static Position findPosition(h.MouseEvent event) {
Position pos1 = doc.findPosition(event.client.x, event.client.y);
if (pos1 == null)
return(null);
pos1.moveInsideTextNodeIfPossible();
assert(pos1.dn != null);
/*
* we can't use window.getSelection in a MouseDown event,
* we need another way to get the position inside text, see DaxeNode.findPosition
if (pos1.daxeNode.nodeType == DaxeNode.TEXT_NODE) {
h.DomSelection selection = h.window.getSelection();
if (selection.rangeCount != 0) {
h.Range r = selection.getRangeAt(0);
if (r.startContainer == r.endContainer && r.startOffset == r.endOffset &&
r.startContainer.parent.classes.contains('dn')) {
// with "white-space: pre-wrap", bad position with a click to the right
// of a newline caused by wrap
Position pos2 = new Position.fromHTML(r.startContainer, r.startOffset);
if (pos2.daxeNode == pos1.daxeNode)
return(pos2);
}
}
}
*/
return(pos1);
}
void keyDown(h.KeyboardEvent event) {
if (selectionStart == null)
return;
bool ctrl = event.ctrlKey || event.metaKey;
bool shift = event.shiftKey;
int keyCode = event.keyCode;
if (ctrl && keyCode == h.KeyCode.X) {
ta.value = copy();
ta.select();
} else if (ctrl && keyCode == h.KeyCode.C) {
ta.value = copy();
ta.select();
} else if (keyCode == h.KeyCode.PAGE_DOWN) {
pageDown();
} else if (keyCode == h.KeyCode.PAGE_UP) {
pageUp();
} else if (keyCode == h.KeyCode.END) {
lineEnd();
} else if (keyCode == h.KeyCode.HOME) {
lineStart();
} else if (keyCode == h.KeyCode.LEFT) {
if (shift)
shiftLeft();
else
left();
} else if (keyCode == h.KeyCode.UP) {
up();
} else if (keyCode == h.KeyCode.RIGHT) {
if (shift)
shiftRight();
else
right();
} else if (keyCode == h.KeyCode.DOWN) {
down();
} else if (keyCode == h.KeyCode.BACKSPACE) {
backspace();
} else if (keyCode == h.KeyCode.DELETE) {
suppr();
} else if (ctrl && shortcuts[keyCode] != null) {
event.preventDefault();
return;
} else if (ta.value != '') {
// note: the first char will only be in ta.value in keyUp, this part
// is only for long-pressed keys
String v = ta.value;
ta.value = '';
doc.insertNewString(v, shift);
} else {
return;
}
newTimer();
}
void keyUp(h.KeyboardEvent event) {
bool ctrl = event.ctrlKey || event.metaKey; // does metaKey work on keyUp ?
bool shift = event.shiftKey;
int keyCode = event.keyCode;
if (selectionStart == null)
return;
if (ctrl && !shift && keyCode == h.KeyCode.Z) { // Ctrl Z
doc.undo();
ta.value = '';
} else if (ctrl && ((!shift && keyCode == h.KeyCode.Y) ||
(shift && keyCode == h.KeyCode.Z))) { // Ctrl-Y and Ctrl-Shift-Z
doc.redo();
ta.value = '';
} else if (ctrl && !shift && keyCode == h.KeyCode.X) { // Ctrl-X
removeSelection();
ta.value = '';
page.updateAfterPathChange();
} else if (ctrl && !shift && keyCode == h.KeyCode.C) { // Ctrl-C
ta.value = '';
} else if (ctrl && !shift && keyCode == h.KeyCode.V) { // Ctrl-V
if (selectionStart != selectionEnd) {
removeSelection();
}
paste(ta.value);
ta.value = '';
page.updateAfterPathChange();
} else if (ctrl && shortcuts[keyCode] != null) {
event.preventDefault();
shortcuts[keyCode]();
page.updateAfterPathChange();
} else if (ta.value != '') {
String v = ta.value;
ta.value = '';
doc.insertNewString(v, shift);
} else {
return;
}
newTimer();
}
void blur(h.Event event) {
hide();
}
/**
* Action for the line start key.
*/
void lineStart() {
Point pt = selectionStart.positionOnScreen();
//pt.x = 0;
// this does not work when blocks are used (it moves the cursor outside)
DaxeNode dn = selectionStart.dn;
if (dn == null)
return;
while (!dn.block && dn.parent != null)
dn = dn.parent;
h.Element hnode = dn.getHTMLNode();
h.Rectangle rect = hnode.getBoundingClientRect();
pt.x = rect.left + 1;
pt.y += 5;
Position pos = doc.findPosition(pt.x, pt.y);
if (pos == null)
return;
if (pos != null) {
moveTo(pos);
page.updateAfterPathChange();
}
}
/**
* Action for the line end key.
*/
void lineEnd() {
Point pt = selectionStart.positionOnScreen();
//pt.x += 10000;
// this does not work when blocks are used (it moves the cursor outside)
DaxeNode dn = selectionStart.dn;
if (dn == null)
return;
while (!dn.block && dn.parent != null)
dn = dn.parent;
h.Element hnode = dn.getHTMLNode();
h.Rectangle rect = hnode.getBoundingClientRect();
pt.x = rect.right - 1;
pt.y += 5;
Position pos = doc.findPosition(pt.x, pt.y);
if (pos == null)
return;
if (pos != null) {
moveTo(pos);
page.updateAfterPathChange();
}
}
/**
* Action for the left arrow key.
*/
void left() {
deSelect();
selectionStart = previousCaretPosition(selectionStart);
selectionEnd = new Position.clone(selectionStart);
updateCaretPosition(true);
page.updateAfterPathChange();
}
/**
* Action for the right arrow key.
*/
void right() {
Position end = new Position.clone(selectionEnd);
end = nextCaretPosition(end);
deSelect();
selectionStart = new Position.clone(end);
selectionEnd = new Position.clone(end);
updateCaretPosition(true);
page.updateAfterPathChange();
}
/**
* Action for the up arrow key.
*/
void up() {
deSelect();
Point pt = selectionStart.positionOnScreen();
if (pt == null)
return;
Position pos2 = selectionStart;
while (pos2 == selectionStart) {
pt.y = pt.y - 7;
pos2 = doc.findPosition(pt.x, pt.y);
pos2.moveInsideTextNodeIfPossible();
}
if (pos2 != null) {
selectionStart = pos2;
selectionEnd = new Position.clone(selectionStart);
}
updateCaretPosition(true);
page.updateAfterPathChange();
}
/**
* Action for the down arrow key.
*/
void down() {
deSelect();
Point pt = selectionStart.positionOnScreen();
if (pt == null)
return;
Position pos2 = selectionStart;
while (pos2 == selectionStart) {
pt.y = pt.y + 14;
pos2 = doc.findPosition(pt.x, pt.y);
pos2.moveInsideTextNodeIfPossible();
}
if (pos2 != null) {
selectionStart = pos2;
selectionEnd = new Position.clone(selectionStart);
}
updateCaretPosition(true);
page.updateAfterPathChange();
}
/**
* Action for the shift + left arrow keys.
*/
void shiftLeft() {
Position start = new Position.clone(selectionStart);
start = previousCaretPosition(start);
setSelection(start, selectionEnd);
}
/**
* Action for the shift + right arrow keys.
*/
void shiftRight() {
Position end = new Position.clone(selectionEnd);
end = nextCaretPosition(end);
setSelection(selectionStart, end);
}
/**
* Action for the page up key.
*/
void pageUp() {
Point pt = selectionStart.positionOnScreen();
if (pt == null)
return;
h.DivElement doc1 = h.document.getElementById('doc1');
pt.y -= doc1.offsetHeight;
Position pos = doc.findPosition(pt.x, pt.y);
if (pos != null) {
int initialScroll = doc1.scrollTop;
moveTo(pos);
doc1.scrollTop = initialScroll - doc1.offsetHeight;
page.updateAfterPathChange();
}
}
/**
* Action for the page down key.
*/
void pageDown() {
Point pt = selectionStart.positionOnScreen();
if (pt == null)
return;
h.DivElement doc1 = h.document.getElementById('doc1');
pt.y += doc1.offsetHeight;
Position pos = doc.findPosition(pt.x, pt.y);
if (pos != null) {
int initialScroll = doc1.scrollTop;
moveTo(pos);
doc1.scrollTop = initialScroll + doc1.offsetHeight;
page.updateAfterPathChange();
}
}
/**
* Action for the backspace key.
*/
void backspace() {
if (selectionStart == selectionEnd) {
DaxeNode dn = selectionStart.dn;
int offset = selectionStart.dnOffset;
if (dn is DNDocument && offset == 0)
return;
// if the cursor is at a newline after a node with an automatic (not DOM) newline,
// the user probably wants to remove the newline instead of the previous node.
if (dn is DNText && offset == 0 && dn.nodeValue[0] == '\n' &&
dn.previousSibling != null && dn.previousSibling.newlineAfter()) {
removeChar(selectionStart);
return;
}
// same thing for newlineInside
if (dn is DNText && offset == 0 && dn.nodeValue[0] == '\n' &&
dn.previousSibling == null && dn.parent.newlineInside()) {
removeChar(selectionStart);
return;
}
// if this is the beginning of a node with no delimiter, remove something
// before instead of the node with no delimiter (unless it's empty)
bool justMovedOutOfBlockWithNoDelimiter = false;
while (dn != null && dn.noDelimiter && offset == 0 && dn.offsetLength > 0) {
if (dn.noDelimiter && dn.block)
justMovedOutOfBlockWithNoDelimiter = true;
else
justMovedOutOfBlockWithNoDelimiter = false;
offset = dn.parent.offsetOf(dn);
dn = dn.parent;
}
if (dn is! DNText && offset > 0) {
DaxeNode prev = dn.childAtOffset(offset - 1);
if (justMovedOutOfBlockWithNoDelimiter) {
// if we're at the beginning of a paragraph and the previous element could
// go inside, move all the previous elements that can inside the paragraph
DaxeNode next = dn.childAtOffset(offset);
assert(next.noDelimiter && next.block);
if (prev.ref != next.ref && !prev.block &&
((prev is DNText && doc.cfg.canContainText(next.ref)) ||
(prev.ref != null && doc.cfg.isSubElement(next.ref, prev.ref)))) {
mergeBlockWithPreviousNodes(next);
return;
}
}
// if the previous node is a paragraph and the next node can move inside,
// move all the following non-block nodes that can inside.
if (prev.noDelimiter && prev.block && offset < dn.offsetLength) {
DaxeNode next = dn.childAtOffset(offset);
if (prev.ref != next.ref && !next.block &&
((next is DNText && doc.cfg.canContainText(prev.ref)) ||
(next.ref != null && doc.cfg.isSubElement(prev.ref, next.ref)))) {
mergeBlockWithNextNodes(prev);
return;
}
}
// move inside previous node with no delimiter, unless 2 paragraphs need to be merged
if (prev.noDelimiter && (!prev.block ||
(offset == dn.offsetLength || dn.childAtOffset(offset).ref != prev.ref))) {
dn = dn.childAtOffset(offset - 1);
offset = dn.offsetLength;
if (dn is! DNText && offset > 0)
prev = dn.childAtOffset(offset - 1);
else
prev = null;
}
}
// if this is the end of a node with no delimiter with a character inside,
// do not remove the whole node, just the last character (except for text nodes)
while ((dn is! DNText && dn.noDelimiter) && dn.offsetLength == offset &&
dn.firstChild != null) {
dn = dn.lastChild;
offset = dn.offsetLength;
}
selectionStart = new Position(dn, offset);
selectionEnd = new Position.clone(selectionStart);
selectionStart.move(-1);
removeChar(selectionStart);
} else {
removeSelection();
}
page.updateAfterPathChange();
}
/**
* Action for the suppr key.
*/
void suppr() {
if (selectionStart == selectionEnd) {
if (selectionStart.dn is DNDocument && selectionStart.dnOffset == selectionStart.dn.offsetLength)
return;
DaxeNode dn = selectionStart.dn;
int offset = selectionStart.dnOffset;
// if at the end, get out of nodes with no delimiter (unless empty)
while (dn.noDelimiter && offset == dn.offsetLength && dn.offsetLength > 0) {
offset = dn.parent.offsetOf(dn) + 1;
dn = dn.parent;
}
if (dn is! DNText && offset > 0 && offset < dn.offsetLength) {
DaxeNode next = dn.childAtOffset(offset);
DaxeNode prev = dn.childAtOffset(offset-1);
// if we're at the end of a paragraph and the next element could
// go inside, move all the next elements that can inside the paragraph
if (prev.noDelimiter && prev.block && next.ref != prev.ref && !next.block &&
((next is DNText && doc.cfg.canContainText(prev.ref)) ||
(next.ref != null && doc.cfg.isSubElement(prev.ref, next.ref)))) {
mergeBlockWithNextNodes(prev);
return;
}
// if the next node is a paragraph and the previous node can move inside,
// move all the previous non-block nodes that can inside.
if (next.noDelimiter && next.block && next.ref != prev.ref && !prev.block) {
if ((prev is DNText && doc.cfg.canContainText(next.ref)) ||
(prev.ref != null && doc.cfg.isSubElement(next.ref, prev.ref))) {
mergeBlockWithPreviousNodes(next);
return;
}
}
}
// move inside next node with no delimiter unless 2 paragraphs need to be merged
if (dn is! DNText && offset < dn.offsetLength) {
DaxeNode next = dn.childAtOffset(offset);
while (next != null && next.noDelimiter && (!next.block ||
(offset == 0 || dn.childAtOffset(offset-1).ref != next.ref))) {
dn = next;
offset = 0;
if (dn is! DNText && offset < dn.offsetLength)
next = dn.childAtOffset(offset);
else
next = null;
}
}
selectionStart = new Position(dn, offset);
selectionEnd = new Position.clone(selectionStart);
removeChar(selectionStart);
} else {
removeSelection();
}
page.updateAfterPathChange();
}
Position nextCaretPosition(Position pos) {
if (pos.dn is DNDocument && pos.dnOffset == pos.dn.offsetLength)
return(pos);
DaxeNode dn = pos.dn;
int offset = pos.dnOffset;
// when at the end, get out of non-block nodes with no delimiter
while (dn != null && dn.noDelimiter && offset == dn.offsetLength && !dn.block) {
offset = dn.parent.offsetOf(dn) + 1;
dn = dn.parent;
}
// if the node at offset is a text or style, move inside
DaxeNode nodeAtOffset;
if (dn.firstChild != null && offset < dn.offsetLength)
nodeAtOffset = dn.childAtOffset(offset);
else
nodeAtOffset = null;
while (nodeAtOffset != null && nodeAtOffset.noDelimiter && !nodeAtOffset.block) {
dn = nodeAtOffset;
offset = 0;
if (dn.firstChild != null && offset < dn.offsetLength)
nodeAtOffset = dn.childAtOffset(offset);
else
nodeAtOffset = null;
}
// visible change of position
// consecutive moves between blocks with no delimiter are not considered cursor moves
bool noDelimiterBlockMove = false;
if (offset == dn.offsetLength) {
// get out of the node
noDelimiterBlockMove = (dn.noDelimiter && dn.block);
offset = dn.parent.offsetOf(dn) + 1;
dn = dn.parent;
while (noDelimiterBlockMove && dn.noDelimiter && dn.block && offset == dn.offsetLength) {
// get out of other no delimiter block nodes
offset = dn.parent.offsetOf(dn) + 1;
dn = dn.parent;
}
} else if (dn is DNText) {
// move in the text
offset++;
} else {
// enter the node
dn = dn.childAtOffset(offset);
// when just entering a node, move to the first cursor position inside
Position first = dn.firstCursorPositionInside();
if (first != null) {
dn = first.dn;
offset = first.dnOffset;
noDelimiterBlockMove = (dn.noDelimiter && dn.block);
} else {
// if there is none, move after this node
offset = dn.parent.offsetOf(dn) + 1;
dn = dn.parent;
}
}
// move inside non-block nodes with no delimiter at current offset
if (dn.firstChild != null && offset < dn.offsetLength)
nodeAtOffset = dn.childAtOffset(offset);
else
nodeAtOffset = null;
while (nodeAtOffset != null && nodeAtOffset.noDelimiter &&
(!nodeAtOffset.block || noDelimiterBlockMove)) {
dn = nodeAtOffset;
offset = 0;
if (dn.firstChild != null && offset < dn.offsetLength)
nodeAtOffset = dn.childAtOffset(offset);
else
nodeAtOffset = null;
}
return(new Position(dn, offset));
}
Position previousCaretPosition(Position pos) {
if (pos.dn is DNDocument && pos.dnOffset == 0)
return(pos);
DaxeNode dn = pos.dn;
int offset = pos.dnOffset;
// when at the beginning, get out of non-block nodes with no delimiter
while (dn != null && dn.noDelimiter && offset == 0 && !dn.block) {
offset = dn.parent.offsetOf(dn);
dn = dn.parent;
}
// if the node before is a text or style, move inside
DaxeNode nodeBefore;
if (dn.firstChild != null && offset > 0)
nodeBefore = dn.childAtOffset(offset - 1);
else
nodeBefore = null;
while (nodeBefore != null && nodeBefore.noDelimiter && !nodeBefore.block) {
dn = nodeBefore;
offset = dn.offsetLength;
if (dn.firstChild != null && offset > 0)
nodeBefore = dn.childAtOffset(offset - 1);
else
nodeBefore = null;
}
// visible change of position
// consecutive moves between blocks with no delimiter are not considered cursor moves
bool noDelimiterBlockMove = false;
if (offset == 0) {
// get out of the node
noDelimiterBlockMove = (dn.noDelimiter && dn.block);
offset = dn.parent.offsetOf(dn);
dn = dn.parent;
while (noDelimiterBlockMove && dn.noDelimiter && dn.block && offset == 0) {
// get out of other no delimiter block nodes
offset = dn.parent.offsetOf(dn);
dn = dn.parent;
}
} else if (dn is DNText) {
// move in the text
offset--;
} else {
// enter the node
dn = dn.childAtOffset(offset-1);
offset = dn.offsetLength;
// when just entering a node, move to the last cursor position inside
Position last = dn.lastCursorPositionInside();
if (last != null) {
dn = last.dn;
offset = last.dnOffset;
noDelimiterBlockMove = (dn.noDelimiter && dn.block);
} else {
// if there is none, move before this node
offset = dn.parent.offsetOf(dn);
dn = dn.parent;
}
}
// move inside non-block nodes with no delimiter before current offset
if (dn.firstChild != null && offset > 0)
nodeBefore = dn.childAtOffset(offset - 1);
else
nodeBefore = null;
while (nodeBefore != null && nodeBefore.noDelimiter &&
(!nodeBefore.block || noDelimiterBlockMove)) {
dn = nodeBefore;
offset = dn.offsetLength;
if (dn.firstChild != null && offset > 0)
nodeBefore = dn.childAtOffset(offset - 1);
else
nodeBefore = null;
}
return(new Position(dn, offset));
}
/**
* Update the caret position when selectionStart == selectionEnd
*/
void updateCaretPosition(bool scroll) {
if (selectionEnd != selectionStart)
return;
Point pt = selectionStart.positionOnScreen();
if (pt == null) {
visible = false;
} else {
visible = true;
h.DivElement doc1 = h.document.getElementById('doc1');
int doctop = doc1.offset.top;
int docheight = doc1.offset.height;
if (pt.y - doctop < 0 || pt.y - doctop > docheight) {
if (scroll) {
doc1.scrollTop += pt.y.toInt() - doctop;
pt = selectionStart.positionOnScreen();
} else {
visible = false;
}
}
}
if (visible) {
caret.style.visibility = 'visible';
caret.style.top = "${pt.y}px";
caret.style.left = "${pt.x}px";
setCaretStyle();
// move and focus the textarea
ta.style.top = "${pt.y}px";
ta.style.left = "${pt.x}px";
ta.focus();
} else {
caret.style.visibility = 'hidden';
}
}
/**
* Sets the caret style (horizontal or vertical)
*/
void setCaretStyle() {
bool horizontal; // horizontal caret between block elements
h.Element hparent = selectionStart.dn.getHTMLNode();
bool parentBlock = _isBlock(hparent);
if (parentBlock && selectionStart.dn.offsetLength > 0) {
bool prevBlock;
if (selectionStart.dnOffset > 0) {
DaxeNode prev = selectionStart.dn.childAtOffset(selectionStart.dnOffset - 1);
h.Element hprev = prev.getHTMLNode();
prevBlock = _isBlock(hprev);
} else {
if (selectionStart.dn is DNWItem)
prevBlock = false; // special case for the beginning of a WYSIWYG list item
else
prevBlock = true;
}
bool nextBlock;
if (selectionStart.dnOffset < selectionStart.dn.offsetLength) {
DaxeNode next = selectionStart.dn.childAtOffset(selectionStart.dnOffset);
h.Element hnext = next.getHTMLNode();
if (next is DNWItem && selectionStart.dnOffset == 0)
nextBlock = false; // special case for the beginning of a WYSIWYG list
else
nextBlock = _isBlock(hnext);
} else
nextBlock = true;
horizontal = prevBlock && nextBlock;
} else
horizontal = false;
if (horizontal)
caret.classes.add('horizontal');
else if (caret.classes.contains('horizontal'))
caret.classes.remove('horizontal');
}
bool _isBlock(h.Element el) {
return(el is h.DivElement || el is h.TableElement || el is h.UListElement || el is h.LIElement);
}
/**
* Moves the caret to the given Position.
*/
void moveTo(Position pos) {
deSelect();
selectionStart = new Position.clone(pos);
selectionStart.moveInsideTextNodeIfPossible();
selectionEnd = new Position.clone(selectionStart);
updateCaretPosition(true);
}
/**
* Hides the cursor.
*/
void hide() {
visible = false;
caret.style.visibility = 'hidden';
}
/**
* Shows the cursor.
*/
void show() {
if (selectionStart != null && selectionStart == selectionEnd) {
visible = true;
caret.style.visibility = 'visible';
}
}
/**
* Obtains the focus.
*/
void focus() {
if (visible)
show();
ta.focus();
}
/**
* Clears the hidden field
*/
void clearField() {
ta.value = '';
}
setSelection(Position start, Position end) {
if (selectionStart == start && selectionEnd == end) {
if (start == end) {
updateCaretPosition(false);
}
return;
}
deSelect();
Position previousStart = selectionStart;
selectionStart = new Position.clone(start);
selectionEnd = new Position.clone(end);
if (selectionStart == selectionEnd) {
//update(selectionStart);
updateCaretPosition(false);
page.updateAfterPathChange();
return;
}
if (selectionStart > selectionEnd) {
Position temp = selectionStart;
selectionStart = selectionEnd;
selectionEnd = temp;
}
// fix selection start and end for styles (different positions look the same for the user)
// and to keep only the elements entirely inside the selection
// move the start and end positions out of text and style if possible
while ((selectionStart.dn is DNText || selectionStart.dn is DNStyle || selectionStart.dn is DNHiddenP) &&
selectionStart.dnOffset == 0) {
selectionStart = new Position(selectionStart.dn.parent,
selectionStart.dn.parent.offsetOf(selectionStart.dn));
}
while ((selectionStart.dn is DNText || selectionStart.dn is DNStyle || selectionStart.dn is DNHiddenP) &&
selectionStart.dnOffset == selectionStart.dn.offsetLength) {
selectionStart = new Position(selectionStart.dn.parent,
selectionStart.dn.parent.offsetOf(selectionStart.dn) + 1);
}
while ((selectionEnd.dn is DNText || selectionEnd.dn is DNStyle || selectionEnd.dn is DNHiddenP) &&
selectionEnd.dnOffset == selectionEnd.dn.offsetLength) {
selectionEnd = new Position(selectionEnd.dn.parent,
selectionEnd.dn.parent.offsetOf(selectionEnd.dn) + 1);
}
while ((selectionEnd.dn is DNText || selectionEnd.dn is DNStyle || selectionEnd.dn is DNHiddenP) &&
selectionEnd.dnOffset == 0) {
selectionEnd = new Position(selectionEnd.dn.parent,
selectionEnd.dn.parent.offsetOf(selectionEnd.dn));
}
// now move positions closer if possible
if (selectionStart != selectionEnd) {
if ((selectionStart.dn is DNText || selectionStart.dn is DNStyle || selectionStart.dn is DNHiddenP) &&
selectionStart.dnOffset == selectionStart.dn.offsetLength) {
DaxeNode next = selectionStart.dn.nextNode();
selectionStart = new Position(next.parent, next.parent.offsetOf(next));
}
if ((selectionEnd.dn is DNText || selectionEnd.dn is DNStyle || selectionEnd.dn is DNHiddenP) &&
selectionEnd.dnOffset == 0) {
DaxeNode prev = selectionEnd.dn.previousNode();
selectionEnd = new Position(prev.parent, prev.parent.offsetOf(prev) + 1);
}
bool cont;
do {
cont = false;
if (selectionStart.dn is! DNText && selectionStart.dnOffset < selectionStart.dn.offsetLength) {
DaxeNode next = selectionStart.dn.childAtOffset(selectionStart.dnOffset);
if (new Position(selectionStart.dn, selectionStart.dnOffset + 1) > selectionEnd &&
new Position(next, 0) < selectionEnd) {
// next is not included and the end is after the beginning of next
selectionStart = new Position(next, 0);
cont = true;
}
}
} while (cont);
do {
cont = false;
if (selectionEnd.dn is! DNText && selectionEnd.dnOffset > 0) {
DaxeNode prev = selectionEnd.dn.childAtOffset(selectionEnd.dnOffset - 1);
if (new Position(selectionEnd.dn, selectionEnd.dnOffset - 1) < selectionStart &&
new Position(prev, prev.offsetLength) > selectionStart) {
// prev is not included and the start is before the end of prev
selectionEnd = new Position(prev, prev.offsetLength);
cont = true;
}
}
} while (cont);
}
if (selectionStart.dn == selectionEnd.dn) {
DaxeNode dn = selectionStart.dn;
if (dn.nodeType == DaxeNode.TEXT_NODE) {
selectText(dn, selectionStart.dnOffset, selectionEnd.dnOffset);
} else {
for (int i = selectionStart.dnOffset; i < selectionEnd.dnOffset; i++) {
DaxeNode child = dn.childAtOffset(i);
child.setSelected(true);
selectedNodes.add(child);
}
}
} else {
DaxeNode startParent = selectionStart.dn;
if (startParent.nodeType == DaxeNode.TEXT_NODE)
startParent = startParent.parent;
if (selectionEnd > new Position(startParent, startParent.offsetLength))
selectionEnd = new Position(startParent, startParent.offsetLength);
else {
DaxeNode endParent = selectionEnd.dn;
if (endParent.nodeType == DaxeNode.TEXT_NODE)
endParent = endParent.parent;
if (endParent != startParent) {
while (endParent.parent != startParent) {
endParent = endParent.parent;
}
selectionEnd = new Position(startParent, startParent.offsetOf(endParent));
}
}
DaxeNode firstNode;
if (selectionStart.dn.nodeType == DaxeNode.ELEMENT_NODE ||
selectionStart.dn.nodeType == DaxeNode.DOCUMENT_NODE) {
firstNode = selectionStart.dn.childAtOffset(selectionStart.dnOffset);
if (firstNode != null) {
Position p2 = new Position(selectionStart.dn, selectionStart.dnOffset + 1);
if (selectionEnd >= p2) {
firstNode.setSelected(true);
selectedNodes.add(firstNode);
}
}
} else {
firstNode = selectionStart.dn;
selectText(firstNode, selectionStart.dnOffset, firstNode.offsetLength);
}
if (firstNode != null) {
for (DaxeNode next = firstNode.nextSibling; next != null; next = next.nextSibling) {
Position p1 = new Position(next.parent, next.parent.offsetOf(next));
if (p1 < selectionEnd) {
if (next.nodeType != DaxeNode.TEXT_NODE ||
selectionEnd >= new Position(next.parent, next.parent.offsetOf(next) + 1)) {
next.setSelected(true);
selectedNodes.add(next);
}
} else
break;
}
}
if (selectionEnd.dn.nodeType == DaxeNode.TEXT_NODE) {
selectText(selectionEnd.dn, 0, selectionEnd.dnOffset);
}
}
if (selectionEnd != selectionStart)
hide();
if (selectionStart != previousStart)
page.updateAfterPathChange();
}
void selectText(DaxeNode dn, int offset1, int offset2) {
h.Element parent = dn.getHTMLNode();
if (parent == null)
return;
h.Text n = parent.nodes.first;
h.Node next = n.nextNode;
hide();
String s = dn.nodeValue;
if (offset1 == 0) {
n.remove();
} else {
n.text = s.substring(0, offset1);
}
h.SpanElement span = new h.SpanElement();
spansSelection.add(span);
span.classes.add('selection');
//span.appendText(s.substring(offset1, offset2));
//see comment in deSelect
span.append(new h.Text(s.substring(offset1, offset2)));
if (next == null)
parent.append(span);
else
parent.insertBefore(span, next);
if (offset2 != s.length) {
h.Text n3 = new h.Text(s.substring(offset2));
if (span.nextNode == null)
parent.append(n3);
else
parent.insertBefore(n3, span.nextNode);
}
}
void deSelect() {
for (h.SpanElement span in spansSelection) {
h.Element parent = span.parent;
StringBuffer sb = new StringBuffer();
for (h.Node hn in parent.nodes) {
sb.write(hn.text);
}
parent.nodes.clear();
// parent.appendText(sb.toString());
// IE9 replaces \n by BR here when appendText is used
// http://code.google.com/p/dart/issues/detail?id=11180
parent.append(new h.Text(sb.toString()));
selectionEnd = new Position.clone(selectionStart);
visible = true;
}
spansSelection.clear();
for (DaxeNode dn in selectedNodes) {
dn.setSelected(false);
}
selectedNodes.clear();
/*
this is causing too many problems (for instance with undo, or text select)
a better solution is to make invisible styles visible (see DNStyle)
if (selectionStart != null && selectionStart == selectionEnd &&
selectionStart.dn is DNStyle &&
selectionStart.dn.firstChild == null) {
// remove an empty style element
DaxeNode toremove = selectionStart.dn;
if (toremove.parent != null) { // otherwise it's already been removed
// we can't do it now, because removing the node can cause text nodes to be merged,
// and this could change the positions passed in a click
Timer.run(() {
print('removed $toremove');
selectionStart = new Position(toremove.parent, toremove.parent.offsetOf(toremove));
selectionEnd = new Position.clone(selectionStart);
doc.removeNode(toremove);
// TODO: automatically undo the creation and removal of the style element
});
}
}
*/
}
void newTimer() {
if (!visible)
return;
if (timer != null)
timer.cancel();
caret.style.visibility = "visible";
timer = new Timer.periodic(delay, (Timer t) => caretBlink());
}
void caretBlink() {
if (!visible)
return;
if (caret.style.visibility == "hidden")
caret.style.visibility = "visible";
else if (caret.style.visibility == "visible")
caret.style.visibility = "hidden";
}
/**
* Removes the first character or Daxe node coming after the cursor.
*/
void removeChar(Position pos) {
DaxeNode toremove;
if (pos.dn.nodeType == DaxeNode.TEXT_NODE &&
pos.dn.offsetLength < pos.dnOffset + 1 &&
pos.dn.nextSibling != null) {
// remove the next node
DaxeNode current = pos.dn;
DaxeNode next = current.nextSibling;
while (next == null && current.parent != null) {
current = current.parent;
next = current.nextSibling;
}
toremove = next;
if (toremove.nodeType == DaxeNode.TEXT_NODE && toremove.parent != null &&
toremove.offsetLength == 1)
toremove = toremove.parent;
} else if (pos.dn.nodeType == DaxeNode.TEXT_NODE &&
pos.dn.offsetLength < pos.dnOffset + 1 &&
pos.dn.nextSibling == null) {
// remove pos.dn's parent
toremove = pos.dn;
if (toremove.parent != null)
toremove = toremove.parent;
if (toremove.noDelimiter && toremove.block) {
if (toremove.nextSibling.ref == toremove.ref) {
// merge the blocks with no delimiter
mergeBlocks(toremove, toremove.nextSibling);
} else {
// remove something just after this character
Position after = new Position.clone(pos);
after.move(1);
if (after > pos)
removeChar(after);
}
return;
}
} else if (pos.dn.nodeType == DaxeNode.ELEMENT_NODE && pos.dn.offsetLength < pos.dnOffset + 1) {
// remove pos.dn
toremove = pos.dn;
if (toremove.noDelimiter && toremove.block) {
if (toremove.nextSibling != null && toremove.nextSibling.ref == toremove.ref) {
// merge the blocks with no delimiter
mergeBlocks(toremove, toremove.nextSibling);
return;
}
}
} else if (pos.dn.nodeType == DaxeNode.ELEMENT_NODE ||
pos.dn.nodeType == DaxeNode.DOCUMENT_NODE) {
toremove = pos.dn.childAtOffset(pos.dnOffset);
if (toremove.noDelimiter && toremove.block) {
if (toremove.previousSibling != null && toremove.previousSibling.ref == toremove.ref) {
// merge the blocks with no delimiter
mergeBlocks(toremove.previousSibling, toremove);
return;
} else if (toremove.offsetLength > 0) {
// remove something just before this character
Position before = new Position.clone(pos);
before.move(-1);
if (before < pos)
removeChar(before);
return;
}
}
if (toremove == null) {
h.window.alert("I'm sorry Dave, I'm afraid I can't do that.");
return;
}
} else if (pos.dn.nodeType == DaxeNode.TEXT_NODE &&
pos.dnOffset == 0 && pos.dn.offsetLength == 1 &&
pos.dn.parent is DNStyle && pos.dn.parent.offsetLength == 1) {
// remove the style node
toremove = pos.dn.parent;
while (toremove.parent is DNStyle && toremove.parent.offsetLength == 1)
toremove = toremove.parent;
} else {
doc.removeString(pos, 1);
// merge styles if possible
EditAndNewPositions ep = DNStyle.mergeAt(selectionStart);
if (ep != null) {
doc.doNewEdit(ep.edit);
doc.combineLastEdits(Strings.get('undo.remove_text'), 2);
setSelection(ep.start, ep.end);
}
return;
}
if (toremove is DNWItem && toremove.parent.offsetLength == 1) {
// remove the whole DNWList when the last DNWItem inside is removed
toremove = toremove.parent;
}
if (!toremove.userCannotRemove) {
doc.removeNode(toremove);
// merge styles if possible
EditAndNewPositions ep = DNStyle.mergeAt(selectionStart);
if (ep != null) {
doc.doNewEdit(ep.edit);
doc.combineLastEdits(Strings.get('undo.remove_element'), 2);
setSelection(ep.start, ep.end);
}
}
}
/**
* Removes everything inside the current selection.
*/
void removeSelection() {
if (selectionStart == selectionEnd)
return;
Position start = new Position.clone(selectionStart);
Position end = new Position.clone(selectionEnd);
deSelect();
if (start.dn is DNWList && start.dn == end.dn && start.dnOffset == 0 &&
end.dnOffset == end.dn.offsetLength) {
// all DNWItem will be removed, the whole DNWList must be removed instead
doc.removeNode(start.dn);
} else {
doc.removeBetween(start, end);
// merge styles if possible
EditAndNewPositions ep = DNStyle.mergeAt(start);
if (ep != null) {
doc.doNewEdit(ep.edit);
doc.combineLastEdits(Strings.get('undo.remove'), 2);
setSelection(ep.start, ep.end);
}
}
}
/**
* Refresh display
*/
void refresh() {
Position start = selectionStart;
Position end = selectionEnd;
selectionStart = null;
selectionEnd = null;
setSelection(start, end);
}
/**
* Returns the current XML selection as a String.
*/
String copy() {
StringBuffer sb = new StringBuffer();
if (selectionStart.dn == selectionEnd.dn) {
DaxeNode dn = selectionStart.dn;
if (dn.nodeType == DaxeNode.TEXT_NODE) {
sb.write(dn.nodeValue.substring(selectionStart.dnOffset, selectionEnd.dnOffset));
} else {
for (int i = selectionStart.dnOffset; i < selectionEnd.dnOffset; i++) {
DaxeNode child = dn.childAtOffset(i);
sb.write(child);
}
}
} else {
DaxeNode firstNode;
if (selectionStart.dn.nodeType == DaxeNode.ELEMENT_NODE) {
firstNode = selectionStart.dn.childAtOffset(selectionStart.dnOffset);
Position p2 = new Position(selectionStart.dn, selectionStart.dnOffset + 1);
if (selectionEnd >= p2) {
sb.write(firstNode);
}
} else {
firstNode = selectionStart.dn;
sb.write(firstNode.nodeValue.substring(selectionStart.dnOffset));
}
for (DaxeNode next = firstNode.nextSibling; next != null; next = next.nextSibling) {
Position p1 = new Position(next.parent, next.parent.offsetOf(next));
if (p1 < selectionEnd) {
if (next.nodeType != DaxeNode.TEXT_NODE ||
selectionEnd >= new Position(next.parent, next.parent.offsetOf(next) + 1)) {
sb.write(next);
next.setSelected(true);
}
} else
break;
}
if (selectionEnd.dn.nodeType == DaxeNode.TEXT_NODE) {
sb.write(selectionEnd.dn.nodeValue.substring(0, selectionEnd.dnOffset));
}
}
return(sb.toString());
}
/**
* Parses the given String and pastes the XML at the current position.
*/
bool paste(String s) {
x.Document tmpdoc;
try {
x.DOMParser dp = new x.DOMParser();
tmpdoc = dp.parseFromString("<root>$s</root>");
} on x.DOMException catch(ex) {
// this is not XML, it is inserted as string if it is possible
bool problem = false;
if (s.trim() != '') {
DaxeNode parent = selectionStart.dn;
if (parent.nodeType == DaxeNode.TEXT_NODE)
parent = parent.parent;
if (parent.nodeType == DaxeNode.DOCUMENT_NODE)
problem = true;
else if (parent.ref != null && !doc.cfg.canContainText(parent.ref))
problem = true;
}
if (problem) {
h.window.alert(Strings.get('insert.text_not_allowed'));
return(false);
}
doc.insertString(selectionStart, s);
return(true);
}
DaxeNode parent = selectionStart.dn;
if (parent is DNText)
parent = parent.parent;
x.Element root = tmpdoc.documentElement;
// to call fixLineBreaks(), we need a real DaxeNode for the "root", with the right ref
DaxeNode dnRoot = NodeFactory.create(parent.ref);
if (root.childNodes != null) {
for (x.Node n in root.childNodes) {
DaxeNode dn = NodeFactory.createFromNode(n, dnRoot);
dnRoot.appendChild(dn);
}
}
dnRoot.fixLineBreaks();
if (doc.hiddenParaRefs != null) {
// add or remove hidden paragraphs where necessary
DNHiddenP.fixFragment(parent, dnRoot);
doc.removeWhitespaceForHiddenParagraphs(dnRoot);
}
UndoableEdit edit = new UndoableEdit.compound(Strings.get('undo.paste'));
try {
edit.addSubEdit(doc.insertChildrenEdit(dnRoot, selectionStart, checkValidity:true));
} on DaxeException catch (ex) {
h.window.alert(ex.toString());
return(false);
}
doc.doNewEdit(edit);
return(true);
}
void mergeBlocks(DaxeNode dn1, DaxeNode dn2) {
UndoableEdit edit = new UndoableEdit.compound(Strings.get('undo.remove_text'));
DaxeNode clone;
Position clonep1 = new Position(dn2, 0);
Position clonep2 = new Position(dn2, dn2.offsetLength);
if (clonep2 > clonep1)
clone = doc.cloneBetween(clonep1, clonep2);
else
clone = null;
edit.addSubEdit(new UndoableEdit.removeNode(dn2));
if (clone != null)
edit.addSubEdit(doc.insertChildrenEdit(clone, new Position(dn1, dn1.offsetLength)));
Position futureCursorPos;
if (dn1.lastChild is DNText)
futureCursorPos = new Position(dn1.lastChild, dn1.lastChild.offsetLength);
else
futureCursorPos = new Position(dn1, dn1.offsetLength);
doc.doNewEdit(edit);
page.moveCursorTo(futureCursorPos);
}
void mergeBlockWithPreviousNodes(DaxeNode dn) {
assert(dn.previousSibling != null);
int offset = dn.parent.offsetOf(dn);
UndoableEdit edit = new UndoableEdit.compound(Strings.get('undo.remove_text'));
// clone the nodes that will move into the paragraph
int startOffset = offset;
bool withText = doc.cfg.canContainText(dn.ref);
while (startOffset > 0) {
DaxeNode child = dn.parent.childAtOffset(startOffset-1);
if (child.block || (child is DNText && !withText) ||
(child.ref != null && !doc.cfg.isSubElement(dn.ref, child.ref)))
break;
startOffset--;
}
Position pStart = new Position(dn.parent, startOffset);
Position currentPos = new Position(dn.parent, offset);
assert (pStart < currentPos);
DaxeNode cloneLeft = doc.cloneCutBetween(dn.parent, pStart, currentPos);
edit.addSubEdit(doc.removeBetweenEdit(pStart, currentPos));
edit.addSubEdit(doc.insertChildrenEdit(cloneLeft, new Position(dn, 0)));
Position futureCursorPos = new Position(dn, 0);
futureCursorPos.moveInsideTextNodeIfPossible();
futureCursorPos = new Position.rightOffsetPosition(futureCursorPos);
doc.doNewEdit(edit);
moveTo(futureCursorPos);
page.updateAfterPathChange();
return;
}
void mergeBlockWithNextNodes(DaxeNode dn) {
assert(dn.nextSibling != null);
int offset = dn.parent.offsetOf(dn.nextSibling);
UndoableEdit edit = new UndoableEdit.compound(Strings.get('undo.remove_text'));
// clone the nodes that will move into the paragraph
int endOffset = offset;
bool withText = doc.cfg.canContainText(dn.ref);
while (endOffset < dn.parent.offsetLength) {
DaxeNode child = dn.parent.childAtOffset(endOffset);
if (child.block || (child is DNText && !withText) ||
(child.ref != null && !doc.cfg.isSubElement(dn.ref, child.ref)))
break;
endOffset++;
}
Position pEnd = new Position(dn.parent, endOffset);
Position currentPos = new Position(dn.parent, offset);
assert (currentPos < pEnd);
DaxeNode cloneRight = doc.cloneCutBetween(dn.parent, currentPos, pEnd);
edit.addSubEdit(doc.removeBetweenEdit(currentPos, pEnd));
edit.addSubEdit(doc.insertChildrenEdit(cloneRight, new Position(dn, dn.offsetLength)));
Position futureCursorPos = new Position(dn, dn.offsetLength);
futureCursorPos.moveInsideTextNodeIfPossible();
futureCursorPos = new Position.leftOffsetPosition(futureCursorPos);
doc.doNewEdit(edit);
moveTo(futureCursorPos);
page.updateAfterPathChange();
return;
}
}
Index: modules/damieng/graphical_editor/daxe/lib/src/daxe_attr.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/daxe_attr.dart
/*
This file is part of Daxe.
Daxe is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Daxe is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Daxe. If not, see <http://www.gnu.org/licenses/>.
*/
part of daxe;
/**
* An attribute.
*/
class DaxeAttr {
String namespaceURI;
String prefix;
String localName;
String value;
DaxeAttr.fromNode(x.Attr a) {
namespaceURI = a.namespaceURI;
prefix = a.prefix;
if (a.prefix != null)
localName = a.localName;
else
localName = a.name;
value = a.nodeValue;
}
DaxeAttr(String name, String value) {
namespaceURI = null;
prefix = null;
localName = name;
this.value = value;
}
DaxeAttr.NS(String namespace, String qualifiedName, String value) {
namespaceURI = namespace;
int ind = qualifiedName.indexOf(':');
if (ind == -1) {
prefix = null;
localName = qualifiedName;
} else {
prefix = qualifiedName.substring(0, ind);
localName = qualifiedName.substring(ind + 1);
}
this.value = value;
}
DaxeAttr.clone(DaxeAttr attr) {
namespaceURI = attr.namespaceURI;
prefix = attr.prefix;
localName = attr.localName;
value = attr.value;
}
String get name {
if (prefix == null)
return(localName);
else
return("$prefix:$localName");
}
String toString() {
StringBuffer sb = new StringBuffer();
if (prefix != null) {
sb.write(prefix);
sb.write(':');
}
sb.write(localName);
sb.write('="');
sb.write(DaxeNode.escape(value));
sb.write('"');
return(sb.toString());
}
}
Index: modules/damieng/graphical_editor/daxe/lib/src/daxe_document.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/daxe_document.dart
/*
This file is part of Daxe.
Daxe is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Daxe is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Daxe. If not, see <http://www.gnu.org/licenses/>.
*/
part of daxe;
/**
* The interface for an XML document.
*/
class DaxeDocument {
int _id_count = 0;
Map<String, DaxeNode> _idToJN = new Map<String, DaxeNode>();
DNDocument dndoc;
Config cfg;
List<UndoableEdit> edits = new List<UndoableEdit>();
int undoPosition = -1;
String filePath;
String saveURL;
List<x.Element> hiddenParaRefs; /* references for hidden paragraphs */
x.Element hiddendiv;
/**
* Create a new document with the given configuration path.
*/
Future newDocument(String configPath) {
Completer completer = new Completer();
cfg = new Config();
cfg.load(configPath).then((_) {
hiddenParaRefs = cfg.elementsWithType('hiddenp');
if (hiddenParaRefs.length == 0)
hiddenParaRefs = null;
hiddendiv = cfg.firstElementWithType('hiddendiv');
filePath = null;
dndoc = new DNDocument();
List<x.Element> roots = cfg.rootElements();
if (roots.length == 1) {
DaxeNode root = NodeFactory.create(roots[0]);
cfg.addNamespaceAttributes(root);
dndoc.appendChild(root);
root.updateValidity();
}
completer.complete();
}, onError: (DaxeException ex) {
completer.completeError(ex);
});
return(completer.future);
}
/**
* Open the document at filePath with the given configuration path.
* If a 404 occurs, create a new document with the config instead
* (so that it can be saved at the given path)
*/
Future openDocument(String filePath, String configPath, {bool removeIndents: true}) {
Completer completer = new Completer();
cfg = new Config();
cfg.load(configPath).then((_) {
hiddenParaRefs = cfg.elementsWithType('hiddenp');
if (hiddenParaRefs.length == 0)
hiddenParaRefs = null;
hiddendiv = cfg.firstElementWithType('hiddendiv');
this.filePath = filePath;
x.DOMParser dp = new x.DOMParser();
dp.parseFromURL(filePath).then((x.Document xmldoc) {
if (removeIndents)
removeWhitespace(xmldoc.documentElement);
dndoc = NodeFactory.createFromNode(xmldoc, null);
if (cfg != null && hiddenParaRefs != null)
removeWhitespaceForHiddenParagraphs(dndoc);
completer.complete();
}, onError: (x.DOMException ex) {
if (ex.errorCode == 404) {
dndoc = new DNDocument();
List<x.Element> roots = cfg.rootElements();
if (roots.length == 1) {
DaxeNode root = NodeFactory.create(roots[0]);
cfg.addNamespaceAttributes(root);
dndoc.appendChild(root);
root.updateValidity();
}
completer.complete();
} else
completer.completeError(new DaxeException("Opening $filePath: $ex"));
});
}, onError: (DaxeException ex) {
completer.completeError(new DaxeException("Reading config $configPath: $ex"));
});
return(completer.future);
}
/**
* Send the document with a POST request to saveURL.
*/
Future saveOnWebJaxe() {
assert(saveURL != null);
Completer completer = new Completer();
String bound = 'AaB03x';
h.HttpRequest request = new h.HttpRequest();
request.onLoad.listen((h.ProgressEvent event) {
String response = request.responseText;
if (response.startsWith('ok'))
completer.complete();
else {
String errorMessage;
if (response.startsWith('erreur\n'))
errorMessage = response.substring('erreur\n'.length);
else
errorMessage = response;
completer.completeError(new DaxeException(errorMessage));
}
});
request.onError.listen((h.ProgressEvent event) {
completer.completeError(new DaxeException(request.status.toString()));
});
request.open('POST', saveURL);
request.setRequestHeader('Content-Type', "multipart/form-data; boundary=$bound");
StringBuffer sb = new StringBuffer();
sb.write("--$bound\r\n");
sb.write('Content-Disposition: form-data; name="chemin"\r\n');
sb.write('Content-type: text/plain; charset=UTF-8\r\n');
sb.write('Content-transfer-encoding: 8bit\r\n\r\n');
sb.write(filePath);
sb.write("\r\n--$bound\r\n");
sb.write('Content-Disposition: form-data; name="contenu"; filename="$filePath"\r\n');
sb.write('Content-Type: application/octet-stream\r\n\r\n');
dndoc.xmlEncoding = 'UTF-8'; // the document is forced to use UTF-8
sb.write(toString());
sb.write('\r\n--$bound--\r\n\r\n');
request.send(sb.toString());
/*
// to use the document encoding (requires the convert library):
// (need support for ArrayBuffer in IE, does not work in IE9)
StringBuffer sb = new StringBuffer();
sb.write("--$bound\r\n");
sb.write('Content-Disposition: form-data; name="chemin"\r\n');
sb.write('Content-type: text/plain; charset=UTF-8\r\n');
sb.write('Content-transfer-encoding: 8bit\r\n\r\n');
sb.write(filePath);
sb.write("\r\n--$bound\r\n");
sb.write('Content-Disposition: form-data; name="contenu"; filename="$filePath"\r\n');
sb.write('Content-Type: application/octet-stream\r\n\r\n');
List<int> buffer = UTF8.encode(sb.toString());
Encoding encoding = Encoding.getByName(dndoc.xmlEncoding);
if (encoding == null) {
print('encoding not supported by Dart: ${dndoc.xmlEncoding} - using UTF-8 instead');
dndoc.xmlEncoding = 'UTF-8';
encoding = UTF8;
}
buffer.addAll(encoding.encode(toString()));
buffer.addAll(UTF8.encode('\r\n--$bound--\r\n\r\n'));
request.send(buffer); // what buffer type can I use here ???
*/
return(completer.future);
}
String newId(DaxeNode jn) {
_id_count++;
String sid = "a$_id_count";
_idToJN[sid] = jn;
return(sid);
}
DaxeNode getNodeById(String id) {
if (id == null)
return(null);
return(_idToJN[id]);
}
h.Element html() {
return(dndoc.html());
}
DaxeNode getRootElement() {
for (DaxeNode dn=dndoc.firstChild; dn != null; dn=dn.nextSibling) {
if (dn.nodeType == DaxeNode.ELEMENT_NODE)
return(dn);
}
return(null);
}
/**
* Inserts a new [DaxeNode] at the given [Position].
* The insert is added to the document history so that it can be undone.
*/
void insertNode(DaxeNode dn, Position pos) {
UndoableEdit edit = new UndoableEdit.insertNode(pos, dn);
doNewEdit(edit);
}
/**
* Removes the given [DaxeNode] from the document.
* The removal is added to the document history so that it can be undone.
*/
void removeNode(DaxeNode dn) {
UndoableEdit edit = new UndoableEdit.removeNode(dn);
doNewEdit(edit);
}
/**
* Inserts a String at the given [Position].
* The insert is added to the document history so that it can be undone.
*/
void insertString(Position pos, String s) {
UndoableEdit edit = new UndoableEdit.insertString(pos, s);
doNewEdit(edit);
}
/**
* Removes a String with the given [length] at the given [Position].
* The removal is added to the document history so that it can be undone.
*/
void removeString(Position pos, int length) {
UndoableEdit edit = new UndoableEdit.removeString(pos, length);
doNewEdit(edit);
}
/**
* Removes everything between [start] and [end].
* The two positions must not cut a node, except for text nodes
* (all the document elements must be either inside or outside the range).
* The removal is added to the document history so that it can be undone.
*/
void removeBetween(Position start, Position end) {
UndoableEdit edit = removeBetweenEdit(start, end);
doNewEdit(edit);
}
/**
* Returns an edit to Remove everything between [start] and [end].
* The two positions must not cut a node, except for text nodes
* (all the document elements must be either inside or outside the range).
*/
UndoableEdit removeBetweenEdit(Position start, Position end) {
assert(start < end);
UndoableEdit edit = new UndoableEdit.compound(Strings.get('undo.remove'));
if (start.dn == end.dn) {
DaxeNode dn = start.dn;
if (dn.nodeType == DaxeNode.TEXT_NODE) {
edit.addSubEdit(new UndoableEdit.removeString(
new Position(dn, start.dnOffset),
end.dnOffset - start.dnOffset));
} else {
// text nodes have to be removed first to avoid text merging problems
for (int i = start.dnOffset; i < end.dnOffset; i++) {
if (dn.childAtOffset(i) is DNText)
edit.addSubEdit(new UndoableEdit.removeNode(dn.childAtOffset(i)));
}
for (int i = start.dnOffset; i < end.dnOffset; i++) {
if (dn.childAtOffset(i) is! DNText)
edit.addSubEdit(new UndoableEdit.removeNode(dn.childAtOffset(i)));
}
}
} else {
bool removeFirstNode = false;
// beginning of the selection
DaxeNode firstNode;
if (start.dn.nodeType == DaxeNode.ELEMENT_NODE) {
firstNode = start.dn.childAtOffset(start.dnOffset);
Position p2 = new Position(start.dn, start.dnOffset + 1);
if (end >= p2)
removeFirstNode = true;
} else {
firstNode = start.dn;
if (firstNode.offsetLength - start.dnOffset > 0) {
edit.addSubEdit(new UndoableEdit.removeString(
new Position(firstNode, start.dnOffset),
firstNode.offsetLength - start.dnOffset));
}
}
// end of the selection
// the nodes are removed at the end to avoid text merge problems
if (end.dn.nodeType == DaxeNode.TEXT_NODE && end.dnOffset > 0) {
edit.addSubEdit(new UndoableEdit.removeString(
new Position(end.dn, 0), end.dnOffset));
}
// between the first and the end node of the selection
// text nodes to remove have to be removed before other nodes to avoid being merged
for (DaxeNode next = firstNode.nextSibling; next != null; next = next.nextSibling) {
Position p1 = new Position(next.parent, next.parent.offsetOf(next));
if (p1 < end) {
if (next.nodeType == DaxeNode.TEXT_NODE &&
end >= new Position(next.parent, next.parent.offsetOf(next) + 1)) {
edit.addSubEdit(new UndoableEdit.removeNode(next));
}
} else
break;
}
if (removeFirstNode)
edit.addSubEdit(new UndoableEdit.removeNode(firstNode));
for (DaxeNode next = firstNode.nextSibling; next != null; next = next.nextSibling) {
Position p1 = new Position(next.parent, next.parent.offsetOf(next));
if (p1 < end) {
if (next.nodeType != DaxeNode.TEXT_NODE) {
edit.addSubEdit(new UndoableEdit.removeNode(next));
}
} else
break;
}
}
return(edit);
}
/**
* Returns a root DaxeNode with a clone of everything between [start] and [end].
* The two positions must not cut a node, except for text nodes
* (all the document elements must be either inside or outside the range).
*/
DaxeNode cloneBetween(Position start, Position end) {
assert(start < end);
DaxeNode parent = start.dn;
if (parent is DNText)
parent = parent.parent;
DaxeNode root = NodeFactory.create(parent.ref);
if (start.dn == end.dn) {
DaxeNode dn = start.dn;
if (dn.nodeType == DaxeNode.TEXT_NODE) {
root.appendChild(new DNText(dn.nodeValue.substring(start.dnOffset, end.dnOffset)));
} else {
for (int i = start.dnOffset; i < end.dnOffset; i++) {
root.appendChild(new DaxeNode.clone(dn.childAtOffset(i)));
}
}
} else {
// beginning of the selection
DaxeNode firstNode;
if (start.dn.nodeType == DaxeNode.ELEMENT_NODE) {
firstNode = start.dn.childAtOffset(start.dnOffset);
Position p2 = new Position(start.dn, start.dnOffset + 1);
if (end >= p2) {
root.appendChild(new DaxeNode.clone(firstNode));
}
} else {
firstNode = start.dn;
if (firstNode.offsetLength - start.dnOffset > 0) {
root.appendChild(new DNText(
firstNode.nodeValue.substring(start.dnOffset, firstNode.offsetLength)));
}
}
// between the first and the end node of the selection
for (DaxeNode next = firstNode.nextSibling; next != null; next = next.nextSibling) {
Position p1 = new Position(next.parent, next.parent.offsetOf(next));
if (p1 < end) {
if (next.nodeType != DaxeNode.TEXT_NODE ||
(next.nodeType == DaxeNode.TEXT_NODE &&
end >= new Position(next.parent, next.parent.offsetOf(next) + 1))) {
root.appendChild(new DaxeNode.clone(next));
}
} else
break;
}
// end of the selection
if (end.dn.nodeType == DaxeNode.TEXT_NODE && end.dnOffset > 0) {
root.appendChild(new DNText(end.dn.nodeValue.substring(0, end.dnOffset)));
}
}
return(root);
}
/**
* Returns a cloned node of root including everything between p1 and p2.
* The interval p1-p2 may cut nodes.
* The root attributes are preserved.
*/
DaxeNode cloneCutBetween(DaxeNode root, Position p1, Position p2) {
while (p1.dnOffset == p1.dn.offsetLength && p1 < p2) {
// to avoid cloning an empty element at the beginning
p1 = new Position(p1.dn.parent, p1.dn.parent.offsetOf(p1.dn) + 1);
}
while (p2.dnOffset == 0 && p2 > p1) {
// to avoid cloning an empty element at the end
p2 = new Position(p2.dn.parent, p2.dn.parent.offsetOf(p2.dn));
}
DaxeNode root2 = new DaxeNode.clone(root);
// optimization: could this shallow clone be optimized without requiring another method for several DaxeNodes ?
// (or we could reuse the children later instead of recloning them)
for (DaxeNode dn = root2.firstChild; dn != null; dn = root2.firstChild)
root2.removeChild(dn);
int offset = 0;
for (DaxeNode dn=root.firstChild; dn != null; dn=dn.nextSibling) {
Position dnstart = new Position(root, offset);
Position dnend = new Position(root, offset + 1);
if (dnstart >= p1 && dnend <= p2) {
DaxeNode dn2 = new DaxeNode.clone(dn);
root2.appendChild(dn2);
} else if (dnend <= p1 || dnstart >= p2) {
// dn is not included at all in the selection
} else {
if (dn is DNText) {
if (dnstart > p1 && dnend > p2) {
assert(dn == p2.dn);
if (0 < p2.dnOffset)
root2.appendChild(new DNText(dn.nodeValue.substring(0, p2.dnOffset)));
} else if (dnstart < p1 && dnend < p2) {
assert(dn == p1.dn);
if (p1.dnOffset < dn.offsetLength)
root2.appendChild(new DNText(dn.nodeValue.substring(p1.dnOffset, dn.offsetLength)));
} else {
// dnstart <= p1 && dnend >= p2
int offset1;
if (p1.dn == dn)
offset1 = p1.dnOffset;
else
offset1 = 0;
int offset2;
if (p2.dn == dn)
offset2 = p2.dnOffset;
else
offset2 = dn.offsetLength;
if (offset1 < offset2)
root2.appendChild(new DNText(dn.nodeValue.substring(offset1, offset2)));
}
} else {
DaxeNode dn2 = cloneCutBetween(dn, p1, p2);
root2.appendChild(dn2);
}
}
offset++;
}
return(root2);
}
/**
* Returns an edit to insert all the children of [root] at position [pos].
* Throws a DaxeException on error.
*/
UndoableEdit insertChildrenEdit(DaxeNode root, Position pos, {bool checkValidity: true}) {
if (pos.dn is DNText && pos.dnOffset == 0) // this position might move
pos = new Position(pos.dn.parent, pos.dn.parent.offsetOf(pos.dn));
DaxeNode parent = pos.dn;
int offset = pos.dnOffset;
if (parent is DNText) {
offset = parent.parent.offsetOf(parent);
parent = parent.parent;
}
UndoableEdit edit = new UndoableEdit.compound('insertChildren');
// extract children from the root to avoid text merging under root
List<DaxeNode> children = new List<DaxeNode>();
while (root.firstChild != null) {
children.add(root.firstChild);
root.removeChild(root.firstChild);
}
// inserting non-text nodes first to avoid text merging issues in the target
Position insertPos = new Position.clone(pos);
for (DaxeNode dn in children) {
if (dn is! DNText) {
if (checkValidity &&
(parent is DNComment || parent is DNCData || parent is DNProcessingInstruction)) {
String title;
if (dn.ref == null)
title = dn.nodeName;
else
title = cfg.elementTitle(dn.ref);
throw new DaxeException(title + ' ' + Strings.get('insert.not_authorized_here'));
}
if (dn is DNCData) {
if (checkValidity && parent.nodeType == DaxeNode.DOCUMENT_NODE)
throw new DaxeException(Strings.get('insert.text_not_allowed'));
String value = null;
if (dn.firstChild != null)
value = dn.firstChild.nodeValue;
if (value == null)
value = '';
if (checkValidity && value.trim() != '' && !cfg.canContainText(parent.ref)) {
throw new DaxeException(Strings.get('insert.text_not_allowed'));
}
edit.addSubEdit(new UndoableEdit.insertNode(insertPos, dn));
} else {
if (checkValidity) {
if (parent.nodeType == DaxeNode.DOCUMENT_NODE) {
if (!cfg.rootElements().contains(dn.ref))
throw new DaxeException(cfg.elementTitle(dn.ref) + ' ' + Strings.get('insert.not_authorized_here'));
} else if (dn is! DNComment && dn is! DNProcessingInstruction) {
if (dn.ref == null || !cfg.isSubElement(parent.ref, dn.ref)) {
String title;
if (dn.ref == null)
title = dn.nodeName;
else
title = cfg.elementTitle(dn.ref);
String parentTitle = cfg.elementTitle(parent.ref);
throw new DaxeException(title + ' ' + Strings.get('insert.not_authorized_inside') + ' ' + parentTitle);
}
if (!cfg.insertIsPossible(parent, offset, offset, dn.ref)) {
throw new DaxeException(cfg.elementTitle(dn.ref) + ' ' + Strings.get('insert.not_authorized_here'));
}
}
}
edit.addSubEdit(new UndoableEdit.insertNode(insertPos, dn));
}
if (insertPos.dn is DNText) // after first insert inside a text node
insertPos = new Position(parent, offset + 2);
else
insertPos = new Position(parent, insertPos.dnOffset + 1);
}
}
// Now copy text nodes.
// If the first text node merges to the left, it will be taken into
// account for the insert positions of the following text nodes.
bool merge = false;
bool first = true;
for (DaxeNode dn in children) {
if (dn is DNText) {
if (checkValidity && parent.nodeType == DaxeNode.DOCUMENT_NODE)
throw new DaxeException(Strings.get('insert.text_not_allowed'));
String value = dn.nodeValue;
if (value == null)
value = '';
if (checkValidity && value.trim() != '' && !cfg.canContainText(parent.ref)) {
throw new DaxeException(Strings.get('insert.text_not_allowed'));
}
int insertOffset = offset + children.indexOf(dn);
if (pos.dn is DNText)
insertOffset++;
if (merge)
insertOffset--;
if (children.length == 1)
insertPos = pos; // case of a single text node
else
insertPos = new Position(parent, insertOffset);
if (first) {
if (insertOffset > 0 && (pos.dn is DNText ||
parent.childAtOffset(insertOffset - 1) is DNText))
merge = true;
first = false;
}
edit.addSubEdit(new UndoableEdit.insertString(insertPos, dn.nodeValue));
}
}
return(edit);
}
String toString() {
return(dndoc.toString());
}
/**
* Returns a new DOM document matching this document.
*/
x.Document toDOMDoc() {
x.DOMImplementation domimpl = new x.DOMImplementationImpl();
x.Document domdoc = domimpl.createDocument(null, null, null);
dndoc.toDOMNode(domdoc);
return(domdoc);
}
/**
* Executes a new edit and adds it to the history so that it can be undone.
*/
void doNewEdit(UndoableEdit edit) {
edit.doit();
if (undoPosition < edits.length - 1)
edits.removeRange(undoPosition + 1, edits.length);
if (undoPosition < 0 || !edits[undoPosition].addEdit(edit)) {
edits.add(edit);
undoPosition++;
}
page.updateUndoMenus();
}
void undo() {
if (undoPosition < 0)
return;
UndoableEdit edit = edits[undoPosition];
edit.undo();
undoPosition--;
page.updateUndoMenus();
page.updateAfterPathChange();
}
void redo() {
if (undoPosition >= edits.length - 1)
return;
UndoableEdit edit = edits[undoPosition + 1];
edit.doit();
undoPosition++;
page.updateUndoMenus();
page.updateAfterPathChange();
}
bool isUndoPossible() {
return(undoPosition >= 0);
}
bool isRedoPossible() {
return(undoPosition < edits.length - 1);
}
/**
* Returns the title for the undo menu.
*/
String getUndoTitle() {
String title;
if (undoPosition >= 0)
title = edits[undoPosition].title;
else
title = null;
if (title == null)
return(Strings.get('undo.undo'));
else
return("${Strings.get('undo.undo')} $title");
}
/**
* Returns the title for the redo menu.
*/
String getRedoTitle() {
String title;
if (undoPosition < edits.length - 1)
title = edits[undoPosition + 1].title;
else
title = null;
if (title == null)
return(Strings.get('undo.redo'));
else
return("${Strings.get('undo.redo')} $title");
}
/**
* Combine the last nb edits into a single compound edit.
*/
void combineLastEdits(String title, int nb) {
UndoableEdit edit = new UndoableEdit.compound(title);
for (int i=edits.length-nb; i<edits.length; i++)
edit.addSubEdit(edits[i]);
edits.removeRange(edits.length - nb, edits.length);
edits.add(edit);
undoPosition -= (nb - 1);
}
/**
* Called when the user tries to insert text.
* [shift]: true if the shift key was used.
*/
void insertNewString(String s, bool shift) {
Position selectionStart = page.getSelectionStart();
Position selectionEnd = page.getSelectionEnd();
if (s == '\n' && !shift) {
// check for newlines in lists
if (((selectionStart.dn is DNItem) ||
(selectionStart.dn.nextSibling == null && selectionStart.dn.parent is DNItem)) &&
selectionStart.dnOffset == selectionStart.dn.offsetLength) {
// \n at the end of a DNList item: adding a new list item
DNList.newlineInItem(selectionStart);
return;
} else {
DaxeNode ancestor = selectionStart.dn;
while (ancestor is DNText || ancestor is DNHiddenP || ancestor is DNStyle || ancestor is DNStyleSpan)
ancestor = ancestor.parent;
if (ancestor is DNWItem) {
// \n in a DNWList item: adding a new list item
DNWList.newlineInItem(selectionStart);
return;
}
}
}
// Handles automatic insertion of hidden paragraphs
if (s == '\n' && hiddenParaRefs != null) {
if (DNHiddenP.handleNewlineOnSelection())
return;
}
// check if text is allowed here
bool problem = false;
DaxeNode parent = selectionStart.dn;
if (parent.nodeType == DaxeNode.TEXT_NODE)
parent = parent.parent;
if (parent.userCannotEdit)
problem = true;
else if (s.trim() != '') {
if (parent.nodeType == DaxeNode.DOCUMENT_NODE)
problem = true;
else if (parent.ref != null && !doc.cfg.canContainText(parent.ref)) {
if (hiddenParaRefs != null) {
x.Element hiddenp = cfg.findSubElement(parent.ref, hiddenParaRefs);
if (hiddenp != null && selectionStart == selectionEnd) {
// we just need to insert a hidden paragraph
DNHiddenP p = new DNHiddenP.fromRef(hiddenp);
p.appendChild(new DNText(s));
insertNode(p, selectionStart);
page.moveCursorTo(new Position(p, p.offsetLength));
page.updateAfterPathChange();
return;
} else
problem = true;
} else
problem = true;
}
}
if (problem) {
h.window.alert(Strings.get('insert.text_not_allowed'));
page.cursor.clearField();
return;
}
bool remove = false;
if (selectionStart != selectionEnd) {
selectionStart = new Position.clone(selectionStart);
selectionEnd = new Position.clone(selectionEnd);
page.cursor.deSelect();
removeBetween(selectionStart, selectionEnd);
remove = true;
//selectionStart.dn.parent is now null if all the text inside an element was removed
if (selectionStart.dn.parent == null)
selectionStart = page.getSelectionStart();
}
doc.insertString(selectionStart, s);
if (remove) {
UndoableEdit edit = new UndoableEdit.compound(Strings.get('undo.insert_text'));
edit.addSubEdit(edits.removeAt(edits.length - 2));
edit.addSubEdit(edits.removeLast());
edits.add(edit);
undoPosition -= 1;
}
}
/**
* Creates and inserts a new Daxe node at the cursor position, displaying the attribute dialog if
* it can have attributes.
* The insert is added to the edits history so that it can be undone.
*/
void insertNewNode(x.Element ref, String nodeType) {
Position pos = page.getSelectionStart();
if (pos == null)
return;
DaxeNode dn = NodeFactory.create(ref, nodeType);
if (nodeType == 'element' && getRootElement() == null) {
cfg.addNamespaceAttributes(dn);
}
dn.newNodeCreationUI(() => insert2(dn, pos));
}
/**
* Inserts the [DaxeNode] at the given [Position] following a user action,
* after the attribute dialog has been validated (if there are attributes).
* If there is a selection, its contents are moved inside the new element.
* The whole operation is added to the document history so that it can be undone.
* Returns true if the insert worked.
* This method is called by [insertNewNode].
*/
bool insert2(DaxeNode dn, Position pos) {
DaxeNode content = null;
if (page.getSelectionStart() != page.getSelectionEnd()) {
Position selectionStart = page.getSelectionStart();
Position selectionEnd = page.getSelectionEnd();
content = cloneBetween(selectionStart, selectionEnd);
page.cursor.deSelect();
if (pos.dn is! DNText && pos.dnOffset > 0 &&
pos.dn.childAtOffset(pos.dnOffset - 1) is DNText) {
// to prevent a problem if the text to the left of the selection is
// merged with the text to the right after the removal
// (pos would be wrong):
DaxeNode prev = pos.dn.childAtOffset(pos.dnOffset - 1);
pos = new Position(prev, prev.offsetLength);
}
removeBetween(selectionStart, selectionEnd);
if (pos.dn.parent == null)
pos = page.getSelectionStart();
}
bool inserted = false;
DaxeNode parent = pos.dn;
if (parent is DNText)
parent = parent.parent;
if (parent is DNHiddenP) {
DNHiddenP p = parent;
if (!cfg.isSubElement(p.ref, dn.ref)) {
// The node must be inserted outside of the paragraph.
// If there is something in the paragraph to the right of the cursor, it must be
// moved into a new paragraph after the inserted node.
UndoableEdit edit = new UndoableEdit.compound(Strings.get('undo.insert_text'));
Position pend = new Position(p, p.offsetLength);
pend.moveInsideTextNodeIfPossible();
if (pos < pend) {
DaxeNode clone = cloneBetween(pos, pend);
edit.addSubEdit(removeBetweenEdit(pos, pend));
DNHiddenP newp = NodeFactory.create(p.ref);
edit.addSubEdit(new UndoableEdit.insertNode(
new Position(p.parent, p.parent.offsetOf(p) + 1), newp));
edit.addSubEdit(insertChildrenEdit(clone, new Position(newp, 0)));
}
edit.addSubEdit(new UndoableEdit.insertNode(
new Position(p.parent, p.parent.offsetOf(p)+1), dn));
doNewEdit(edit);
inserted = true;
}
} else if (hiddenParaRefs != null && parent.ref != null && !cfg.isSubElement(parent.ref, dn.ref)) {
x.Element hiddenp = cfg.findSubElement(parent.ref, hiddenParaRefs);
if (hiddenp != null && cfg.isSubElement(hiddenp, dn.ref)) {
// a new paragraph must be created
DNHiddenP p = new DNHiddenP.fromRef(hiddenp);
p.appendChild(dn);
insertNode(p, pos);
inserted = true;
}
}
if (!inserted)
insertNode(dn, pos);
Position cursorPos = dn.firstCursorPositionInside();
if (cursorPos == null)
cursorPos = new Position(dn.parent, dn.parent.offsetOf(dn) + 1);
page.moveCursorTo(cursorPos);
page.updateAfterPathChange();
if (content != null) {
try {
if (cursorPos == null)
throw new DaxeException(content.toString() + Strings.get('insert.not_authorized_here'));
if (doc.hiddenParaRefs != null) {
// add or remove hidden paragraphs where necessary
DNHiddenP.fixFragment(dn, content);
}
doNewEdit(insertChildrenEdit(content, cursorPos, checkValidity:true));
// replace the 3 previous edits by a compound
combineLastEdits(Strings.get('undo.insert_element'), 3);
page.updateUndoMenus();
return(true);
} on DaxeException catch (ex) {
h.window.alert(ex.toString());
// inserting content didn't work: undo remove and insert
undo();
undo();
edits.removeRange(edits.length - 2, edits.length);
page.updateUndoMenus();
return(false);
}
}
return(true);
}
void executeFunction(String functionName, x.Element el) {
// TODO: pass the parameters in el to the functions
if (functionName == 'jaxe.FonctionNormal') {
DNStyle.removeStylesFromSelection();
} else if (customFunctions[functionName] != null)
customFunctions[functionName]();
}
/**
* Returns the list of element references which can be allowed under a given parent Daxe node.
* For the document node, returns the list of possible root elements.
* For a text node, returns the list of elements references allowed under its parent.
*/
List<x.Element> elementsAllowedUnder(DaxeNode dn) {
List<x.Element> refs;
if (dn.nodeType == DaxeNode.DOCUMENT_NODE) {
refs = doc.cfg.rootElements();
} else if (dn.ref == null) {
refs = new List<x.Element>();
} else {
DaxeNode parent;
if (dn.nodeType == DaxeNode.TEXT_NODE)
parent = dn.parent;
else
parent = dn;
refs = cfg.subElements(parent.ref);
if (parent is DNHiddenP && parent.parent.ref != null) {
LinkedHashSet set = new LinkedHashSet.from(refs);
set.addAll(cfg.subElements(parent.parent.ref));
refs = new List.from(set);
} else if (hiddenParaRefs != null) {
x.Element hiddenp = cfg.findSubElement(parent.ref, hiddenParaRefs);
if (hiddenp != null) {
LinkedHashSet set = new LinkedHashSet.from(refs);
set.addAll(cfg.subElements(hiddenp));
refs = new List.from(set);
}
}
if (parent.restrictedInserts != null) {
for (int i=0; i<refs.length; i++) {
if (!parent.restrictedInserts.contains(cfg.elementName(refs[i]))) {
refs.removeAt(i);
i--;
}
}
}
}
return(refs);
}
/**
* Given a list of element references, returns the elements in this list which can
* be inserted over the current selection.
*/
List<x.Element> validElementsInSelection(List<x.Element> allowed) {
List<x.Element> list = new List<x.Element>();
Position selectionStart = page.getSelectionStart();
Position selectionEnd = page.getSelectionEnd();
DaxeNode startParent = selectionStart.dn;
int startOffset = selectionStart.dnOffset;
DaxeNode endParent = selectionEnd.dn;
int endOffset = selectionEnd.dnOffset;
if (startParent.nodeType == DaxeNode.TEXT_NODE) {
startOffset = startParent.parent.offsetOf(startParent);
startParent = startParent.parent;
}
if (endParent.nodeType == DaxeNode.TEXT_NODE) {
endOffset = endParent.parent.offsetOf(endParent);
endParent = endParent.parent;
}
if (startParent != endParent) // this should not happen
return(list);
for (x.Element ref in allowed) {
if (doc.cfg.insertIsPossible(startParent, startOffset, endOffset, ref))
list.add(ref);
}
if (startParent is DNHiddenP && startParent.parent.ref != null) {
int offset = startParent.parent.offsetOf(startParent) + 1;
LinkedHashSet set = new LinkedHashSet.from(list);
for (x.Element ref in allowed) {
if (!set.contains(ref)) {
if (doc.cfg.insertIsPossible(startParent.parent, offset, offset, ref))
set.add(ref);
}
}
list = new List.from(set);
} else if (hiddenParaRefs != null) {
x.Element hiddenp = null;
for (x.Element ref in hiddenParaRefs)
if (list.contains(ref)) {
hiddenp = ref;
break;
}
if (hiddenp != null) {
LinkedHashSet set = new LinkedHashSet.from(list);
for (x.Element ref in allowed) {
if (!set.contains(ref)) {
if (doc.cfg.isSubElement(hiddenp, ref))
set.add(ref);
}
}
list = new List.from(set);
}
}
return(list);
}
/**
* Returns the [Position] matching the given screen coordinates.
*/
Position findPosition(num x, num y) {
return(dndoc.findPosition(x, y));
}
/**
* Recusively removes needless whitespaces in the element.
*/
void removeWhitespace(final x.Element el) {
_removeWhitespace(el, null, false, true);
}
void _removeWhitespace(final x.Element el, final x.Element refParent, final bool spacePreserveParent,
final bool fteParent) {
x.Element refElement;
if (cfg != null)
refElement = cfg.getElementRef(el, refParent);
else
refElement = null;
bool spacePreserve = _spacePreserve(el, refElement, refParent, spacePreserveParent);
final bool fte = _isFirstTextElement(el, refElement, refParent, fteParent);
x.Node next;
for (x.Node n = el.firstChild; n != null; n = next) {
next = n.nextSibling;
if (n.nodeType == x.Node.ELEMENT_NODE)
_removeWhitespace(n as x.Element, refElement, spacePreserve, fte);
else if (!spacePreserve && n.nodeType == x.Node.TEXT_NODE) {
String s = n.nodeValue;
// whitespace is not removed if there is only whitespace in the element
//if (n.nextSibling == null && n.previousSibling == null && s.trim() == "")
// break;
if (fte && n.parentNode.firstChild == n && refParent != null) {
// remove whitespace at the beginning if the text node is the first child of the element
int ifin = 0;
while (ifin < s.length && (s[ifin] == ' ' || s[ifin] == '\t'))
ifin++;
if (ifin > 0)
s = s.substring(ifin);
}
// remove spaces after newlines
int idebut = s.indexOf("\n ");
int idebuttab = s.indexOf("\n\t");
if (idebuttab != -1 && (idebut == -1 || idebuttab < idebut))
idebut = idebuttab;
while (idebut != -1) {
int ifin = idebut;
while (ifin + 1 < s.length && (s[ifin + 1] == ' ' || s[ifin + 1] == '\t'))
ifin++;
s = s.substring(0, idebut+1) + s.substring(ifin+1);
idebut = s.indexOf("\n ");
idebuttab = s.indexOf("\n\t");
if (idebuttab != -1 && (idebut == -1 || idebuttab < idebut))
idebut = idebuttab;
}
// condense spaces everywhere
idebut = s.indexOf(" ");
while (idebut != -1) {
int ifin = idebut;
while (ifin + 1 < s.length && s[ifin + 1] == ' ')
ifin++;
s = s.substring(0, idebut) + s.substring(ifin);
idebut = s.indexOf(" ");
}
// update the node
if (s.length == 0)
el.removeChild(n);
else
n.nodeValue = s;
}
}
}
/**
* Returns true if the text should be preserved in an element.
*/
bool _spacePreserve(final x.Element el, final x.Element refElement, final x.Element refParent,
final bool spacePreserveParent) {
bool spacePreserve;
final String xmlspace = el.getAttribute("xml:space");
if (xmlspace == "preserve")
spacePreserve = true;
else if (xmlspace == "default")
spacePreserve = false;
else
spacePreserve = spacePreserveParent;
if (refElement != null && xmlspace == "") {
final List<x.Element> attributs = cfg.elementAttributes(refElement);
for (x.Element attref in attributs) {
if (cfg.attributeName(attref) == "space" &&
cfg.attributeNamespace(attref) == "http://www.w3.org/XML/1998/namespace") {
final String defaut = cfg.defaultAttributeValue(attref);
if (defaut == "preserve")
spacePreserve = true;
else if (defaut == "default")
spacePreserve = false;
break;
}
}
}
return(spacePreserve);
}
/**
* Returns false if whitespaces should not be removed at the start of the element.
*/
bool _isFirstTextElement(final x.Element el, final x.Element refEl, final x.Element refParent,
final bool fteParent) {
if (refEl == null)
return true;
if (refParent == null || !cfg.canContainText(refEl) || !cfg.canContainText(refParent))
return true;
x.Node prevNode = el.previousSibling;
while (prevNode != null) {
if (prevNode.nodeType == x.Node.TEXT_NODE) {
final String prevText = prevNode.nodeValue;
if (!(prevText.endsWith(" ") || prevText.endsWith("\n")))
return false;
return true;
} else if (prevNode.nodeType == x.Node.ELEMENT_NODE) {
final x.Node lc = prevNode.lastChild;
if (lc != null && lc.nodeType == x.Node.TEXT_NODE) {
final String prevText = lc.nodeValue;
if (!(prevText.endsWith(" ") || prevText.endsWith("\n")))
return false;
}
return true;
}
prevNode = prevNode.previousSibling;
}
if (prevNode == null)
return fteParent;
return true;
}
/**
* Replace newlines by spaces where hidden paragraphs can be found and inside them,
* and remove whitespace before and after blocks where hidden paragraphs can be found.
*/
void removeWhitespaceForHiddenParagraphs(DaxeNode parent) {
x.Element paraRef;
if (parent.ref != null)
paraRef = cfg.findSubElement(parent.ref, hiddenParaRefs);
else
paraRef = null;
bool paraInside = (paraRef != null);
bool para = parent is DNHiddenP;
bool style = parent is DNStyle;
DaxeNode next;
for (DaxeNode dn=parent.firstChild; dn != null; dn=next) {
next = dn.nextSibling;
if (dn is DNText) {
if (paraInside || para || style) {
String s = dn.nodeValue;
// replace newlines by spaces except between XML comments
if (dn.previousSibling == null || dn.previousSibling is! DNComment ||
dn.nextSibling == null || dn.nextSibling is! DNComment) {
s = s.replaceAll('\n', ' ');
}
s = s.replaceAll(' ', ' ');
if (paraInside) {
// also trim left if there is a block before, and right if there is a block after
// ("blocks" here are elements that are not allowed inside a paragraph)
if (dn.previousSibling != null && dn.previousSibling.ref != null && !cfg.isSubElement(paraRef, dn.previousSibling.ref)) {
s = s.trimLeft();
}
if (dn.nextSibling != null && dn.nextSibling.ref != null && !cfg.isSubElement(paraRef, dn.nextSibling.ref)) {
s = s.trimRight();
}
} else if (para) {
// trim hidden paragraphs
if (dn.previousSibling == null)
s = s.trimLeft();
if (dn.nextSibling == null)
s = s.trimRight();
}
if (s.length == 0)
parent.removeChild(dn);
else
dn.nodeValue = s;
}
} else if (dn.firstChild != null)
removeWhitespaceForHiddenParagraphs(dn);
}
}
/**
* Indents a DOM document recursively.
*/
void indentDOMDocument(x.Document domdoc) {
if (domdoc.documentElement != null)
_indentDOMNode(domdoc.documentElement, null, false, 1);
}
/**
* Indents a DOM node recursively.
*/
void _indentDOMNode(final x.Element el, final x.Element refParent, final bool spacePreserveParent, final int level) {
x.Element refElement;
if (cfg != null)
refElement = cfg.getElementRef(el, refParent);
else
refElement = null;
bool spacePreserve = _spacePreserve(el, refElement, refParent, spacePreserveParent);
for (x.Node n = el.firstChild; n != null; n = n.nextSibling) {
if (n.nodeType == x.Node.ELEMENT_NODE)
_indentDOMNode(n as x.Element, refElement, spacePreserve, level+1);
else if (!spacePreserve && n.nodeType == x.Node.TEXT_NODE && n.nodeValue.contains("\n")) {
StringBuffer sb = new StringBuffer("\n");
int indents = level;
if (n.nextSibling == null)
indents--;
for (int i=0; i<indents; i++)
sb.write(' ');
String newline_spaces = sb.toString();
n.nodeValue = n.nodeValue.replaceAll("\n", newline_spaces);
}
}
}
}
Index: modules/damieng/graphical_editor/daxe/lib/src/daxe_exception.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/daxe_exception.dart
/*
This file is part of Daxe.
Daxe is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Daxe is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Daxe. If not, see <http://www.gnu.org/licenses/>.
*/
part of daxe;
class DaxeException implements Exception {
final String message;
final Exception parentException;
const DaxeException([this.message, this.parentException]);
String toString() {
String s;
if (message == null)
s = 'DaxeException';
else
s = message;
if (parentException != null)
s = "$s (parent exception: $parentException)";
return(s);
}
}
Index: modules/damieng/graphical_editor/daxe/lib/src/daxe_node.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/daxe_node.dart
/*
This file is part of Daxe.
Daxe is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Daxe is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Daxe. If not, see <http://www.gnu.org/licenses/>.
*/
part of daxe;
/**
* This class represents a GUI for an XML node. The subclasses offer differents GUIs.
*/
abstract class DaxeNode {
static const int ELEMENT_NODE = 1;
static const int TEXT_NODE = 3;
//NOTE: cdata, pi and comments are now DaxeNode elements containing a text node
//static const int CDATA_SECTION_NODE = 4;
//static const int PROCESSING_INSTRUCTION_NODE = 7;
//static const int COMMENT_NODE = 8;
static const int DOCUMENT_NODE = 9;
static const String STYLE_BOLD = 'GRAS';
static const String STYLE_ITALIC = 'ITALIQUE';
static const String STYLE_SUPERSCRIPT = 'EXPOSANT';
static const String STYLE_SUBSCRIPT = 'INDICE';
static const String STYLE_UNDERLINE = 'SOULIGNE';
static const String STYLE_STRIKETHROUGH = 'BARRE';
static const String STYLE_BACKGROUND_COLOR = 'FCOULEUR';
static const String STYLE_FOREGROUND_COLOR = 'PCOULEUR';
static const String COLOR_PATTERN = "^.*\\[(x[0-9a-fA-F]{2}|[0-9]{1,3}),(x[0-9a-fA-F]{2}|[0-9]{1,3}),(x[0-9a-fA-F]{2}|[0-9]{1,3})\\]\$";
x.Element ref; // schema element
String _id;
DaxeNode parent;
int nodeType;
String _namespaceURI;
String prefix;
String localName;
String nodeValue;
DaxeNode firstChild;
DaxeNode nextSibling;
List<DaxeAttr> attributes;
bool userCannotRemove = false; // with suppr/del, could be extended to selections...
bool userCannotEdit = false;
bool valid;
List<String> restrictedInserts; // used in DaxeDocument.elementsAllowedUnder to restrict inserts beyond schema
/**
* Constructor using a DOM [node] and a DaxeNode [parent].
* Will create children nodes recursively unless [createChildren] is false.
*/
DaxeNode.fromNode(x.Node node, DaxeNode parent, {bool createChildren: true}) {
_id = doc.newId(this);
this.parent = parent;
if (node.nodeType == x.Node.ELEMENT_NODE || node.nodeType == x.Node.TEXT_NODE ||
node.nodeType == x.Node.DOCUMENT_NODE)
nodeType = node.nodeType;
else
nodeType = ELEMENT_NODE;
_namespaceURI = node.namespaceURI;
prefix = node.prefix;
if (node.nodeType == x.Node.PROCESSING_INSTRUCTION_NODE)
localName = node.nodeName;
else if (node.nodeType == x.Node.CDATA_SECTION_NODE)
localName = '#cdata-section';
else if (node.nodeType == x.Node.COMMENT_NODE)
localName = '#comment';
else if (node.nodeType == x.Node.DOCUMENT_NODE)
localName = '#document';
else
localName = node.localName;
if (nodeType == DaxeNode.TEXT_NODE)
nodeValue = node.nodeValue;
attributes = new List<DaxeAttr>();
LinkedHashMap<String, x.Attr> nm = node.attributes;
if (nm != null) {
for (x.Node n in nm.values) {
attributes.add(new DaxeAttr.fromNode(n));
}
}
if (node is x.Element) {
ref = doc.cfg.getElementRef(node, parent == null ? null : parent.ref);
if (ref == null && parent != null) {
// could not find a reference when taking the parent into account
// there is probably an error in the document, but we will try to use
// another reference by ignoring the parent
ref = doc.cfg.elementReference(localName);
}
}
if (createChildren) {
if (node.childNodes != null) {
DaxeNode prev = null;
for (x.Node n in node.childNodes) {
DaxeNode dn = NodeFactory.createFromNode(n, this);
if (prev == null)
firstChild = dn;
else
prev.nextSibling = dn;
prev = dn;
}
} else if ((node.nodeType == x.Node.CDATA_SECTION_NODE ||
node.nodeType == x.Node.PROCESSING_INSTRUCTION_NODE ||
node.nodeType == x.Node.COMMENT_NODE) &&
node.nodeValue != null && node.nodeValue != '') {
appendChild(new DNText(node.nodeValue));
}
}
if (nodeType == DaxeNode.ELEMENT_NODE)
valid = doc.cfg.elementIsValid(this);
else
valid = true;
}
/**
* Constructor using an element reference.
* This will always create an element node.
*/
DaxeNode.fromRef(x.Element elementRef) {
ref = elementRef;
_id = doc.newId(this);
parent = null;
nodeType = ELEMENT_NODE;
_namespaceURI = doc.cfg.elementNamespace(ref);
prefix = doc.cfg.elementPrefix(ref);
localName = doc.cfg.elementName(ref);
nodeValue = null;
firstChild = null;
nextSibling = null;
attributes = new List<DaxeAttr>();
valid = true;
}
/**
* Constructor using a node type.
* Useful to create new document, cdata, pi or comment nodes (they don't have a ref).
* Possible node types are available as constants of this class.
*/
DaxeNode.fromNodeType(int nodeType) {
ref = null;
_id = doc.newId(this);
parent = null;
this.nodeType = nodeType;
_namespaceURI = null;
prefix = null;
localName = null;
nodeValue = null;
firstChild = null;
nextSibling = null;
attributes = new List<DaxeAttr>();
valid = true;
}
/**
* Constructor for a text node with the given [value].
*/
DaxeNode.text(String value) {
_id = doc.newId(this);
parent = null;
nodeType = DaxeNode.TEXT_NODE;
_namespaceURI = null;
this.prefix = null;
this.localName = null;
nodeValue = value;
firstChild = null;
nextSibling = null;
attributes = null;
valid = true;
}
/**
* Deep clone constructor, using the DOM serialization.
*/
factory DaxeNode.clone(DaxeNode dn) {
x.DOMImplementation domimpl = new x.DOMImplementationImpl();
x.Document domdoc = domimpl.createDocument(null, null, null);
x.Node n = dn.toDOMNode(domdoc);
DaxeNode clone = NodeFactory.createFromNode(n, dn.parent);
clone.parent = null;
return(clone);
}
/**
* Id for the corresponding HTML element.
*/
String get id {
return(_id);
}
/**
* Returns the corresponding HTML element.
*/
h.Element getHTMLNode() {
return(h.document.getElementById(_id));
}
/**
* Returns the HTML element in which the contents of the XML element will be displayed.
* This is used in Position to find where to display the cursor when there is no child.
*/
h.Element getHTMLContentsNode() {
h.Element hn = getHTMLNode();
if (hn != null && !hn.nodes.isEmpty && hn.firstChild is h.Element)
hn = hn.firstChild;
return(hn);
}
String get nodeName {
if (nodeType == TEXT_NODE)
return('#text');
StringBuffer buff = new StringBuffer();
if (prefix != null) {
buff.write(prefix);
buff.write(":");
}
buff.write(localName);
return(buff.toString());
}
String get namespaceURI {
return(_namespaceURI);
}
/**
* For a text node, the number of characters in the node value.
* Otherwise, the number of children.
*/
int get offsetLength {
if (nodeType == TEXT_NODE)
return(nodeValue.length);
int n = 0;
for (DaxeNode dn=firstChild; dn != null; dn=dn.nextSibling)
n++;
return(n);
}
/**
* Returns true if this node does not have delimiters such as tags or a box.
* This affects cursor behavior (for instance, a backspace after a tag removes the node,
* and 2 blocks with the same ref can be merged when a backspace is used
* at the beginning of the second block)
*/
bool get noDelimiter {
return(false);
}
/**
* Returns true if this node is displayed like a block
* (such as area, division or hiddenp, with line breaks).
*/
bool get block {
// this is only a guess, it should be subclassed to be safe
if (newlineAfter())
return(true);
h.Element hnode = getHTMLNode();
return(hnode is h.DivElement || hnode is h.TableElement || hnode is h.UListElement);
}
/**
* The child nodes in a convenient list.
*/
List<DaxeNode> get childNodes {
List<DaxeNode> list = new List<DaxeNode>();
for (DaxeNode dn=firstChild; dn != null; dn=dn.nextSibling) {
list.add(dn);
}
return(list);
}
/**
* This node's previous sibling.
*/
DaxeNode get previousSibling {
if (parent == null)
return(null);
for (DaxeNode dn = parent.firstChild; dn != null; dn = dn.nextSibling) {
if (dn.nextSibling == this)
return(dn);
}
return(null);
}
DaxeNode get lastChild {
for (DaxeNode dn = firstChild; dn != null; dn = dn.nextSibling) {
if (dn.nextSibling == null)
return(dn);
}
return(null);
}
DaxeNode childAtOffset(int offset) {
assert(nodeType != TEXT_NODE);
int n = 0;
for (DaxeNode dn=firstChild; dn != null; dn=dn.nextSibling) {
if (n == offset)
return(dn);
n++;
}
return(null);
}
/**
* Returns the next node in the document (excluding attribute nodes).
*/
DaxeNode nextNode() {
if (firstChild != null)
return(firstChild);
if (nextSibling != null)
return(nextSibling);
DaxeNode p = parent;
while (p != null) {
if (p.nextSibling != null)
return(p.nextSibling);
p = p.parent;
}
return(null);
}
/**
* Returns the previous node in the document (excluding attribute nodes).
*/
DaxeNode previousNode() {
if (firstChild != null)
return(lastChild);
if (previousSibling != null)
return(previousSibling);
DaxeNode p = parent;
while (p != null) {
if (p.previousSibling != null)
return(p.previousSibling);
p = p.parent;
}
return(null);
}
/**
* Returns the index of the given child node.
*/
int offsetOf(DaxeNode child) {
int i = 0;
for (DaxeNode n=firstChild; n != null; n=n.nextSibling) {
if (n == child)
return(i);
i++;
}
assert(false);
return(-1);
}
String getAttribute(String name) {
for (DaxeAttr att in attributes) {
if (att.localName == name)
return(att.value);
}
return(null);
}
String getAttributeNS(String namespaceURI, String localName) {
if (attributes == null)
return(null);
for (DaxeAttr att in attributes) {
if (att.namespaceURI == namespaceURI && att.localName == localName)
return(att.value);
}
return(null);
}
void setAttribute(String name, String value) {
for (DaxeAttr att in attributes) {
if (att.localName == name) {
att.value = value;
return;
}
}
attributes.add(new DaxeAttr(name, value));
return;
}
void removeAttribute(String name) {
for (DaxeAttr att in attributes) {
if (att.localName == name) {
attributes.remove(att);
return;
}
}
}
void setAttributeNS(String namespaceURI, String qualifiedName, String value) {
String attPrefix, attLocalName;
int ind = qualifiedName.indexOf(":");
if (ind != -1) {
attPrefix = qualifiedName.substring(0, ind);
attLocalName = qualifiedName.substring(ind+1);
} else {
attPrefix = null;
attLocalName = qualifiedName;
}
DaxeAttr att = getAttributeNodeNS(namespaceURI, attLocalName);
if (att != null) {
att.prefix = attPrefix;
att.value = value;
return;
}
att = new DaxeAttr.NS(namespaceURI, qualifiedName, value);
attributes.add(att);
}
DaxeAttr getAttributeNode(String name) {
for (DaxeAttr att in attributes) {
if (att.localName == name)
return(att);
}
return(null);
}
DaxeAttr getAttributeNodeNS(String namespaceURI, String localName) {
if (attributes == null)
return(null);
for (DaxeAttr att in attributes) {
if (att.namespaceURI == namespaceURI && att.localName == localName) {
return(att);
}
}
return(null);
}
LinkedHashMap<String, DaxeAttr> getAttributesMapCopy() {
LinkedHashMap<String, DaxeAttr> map = new LinkedHashMap<String, DaxeAttr>();
for (DaxeAttr attr in attributes)
map[attr.name] = new DaxeAttr.clone(attr);
return(map);
}
/**
* Creates and returns the HTML element for this DaxeNode.
* This abstract method must be overriden by subclasses.
*/
h.Element html();
/**
* Update the display. By default, this recreates all the HTML.
*/
void updateHTML() {
h.Element vel = getHTMLNode();
if (vel == null)
return;
h.Element nel = html();
vel.replaceWith(nel);
}
/**
* Update the display after children changed (insert/removal/other).
* This method can be overriden for optimization.
*/
void updateHTMLAfterChildrenChange(List<DaxeNode> changed) {
List<DaxeNode> children = childNodes;
for (DaxeNode child in changed) {
h.Element hn = child.getHTMLNode();
if (!children.contains(child)) {
// removal
if (hn != null)
hn.remove();
else {
// no reference to the HTML node, only the derived class
// could know how to remove the child display
updateHTML();
return;
}
} else if (hn == null) {
// insert
DaxeNode next = child.nextSibling;
h.Node nextHn = null;
while (nextHn == null && next != null) {
nextHn = next.getHTMLNode();
if (nextHn == null)
next = next.nextSibling;
}
DaxeNode prev = child.previousSibling;
h.Node prevHn = null;
while (prevHn == null && prev != null) {
prevHn = prev.getHTMLNode();
if (prevHn == null)
prev = prev.previousSibling;
}
if (nextHn != null) {
nextHn.parent.insertBefore(child.html(), nextHn);
} else if (prevHn != null) {
// there might be some nodes at the end of the parent that we want
// to keep at the end (for instance the space at the end of a cell)
if (prevHn.nextNode != null)
prevHn.parent.insertBefore(child.html(), prevHn.nextNode);
else
prevHn.parent.append(child.html());
} else {
// no sibling, there might not even be a content div...
//TODO getHTMLContentsNode
updateHTML();
return;
}
} else {
// change
child.updateHTML();
}
}
}
/**
* The attributes have changed, an update might be needed.
* This method can be overriden for optimization.
*/
void updateAttributes() {
updateHTML();
}
/**
* Sets whether this node is selected by the user or not.
*/
void setSelected(bool select) {
h.Element hn = getHTMLNode();
if (hn == null)
return;
if (select)
hn.classes.add('selected');
else
hn.classes.remove('selected');
}
void appendChild(DaxeNode dn) {
DaxeNode last = lastChild;
if (last != null)
last.nextSibling = dn;
else
firstChild = dn;
dn.parent = this;
}
/**
* Inserts [newdn] as a child of this node before [beforedn].
* beforedn may be null, in which case it is inserted as the last child.
*/
void insertBefore(DaxeNode newdn, DaxeNode beforedn) {
assert(beforedn == null || this == beforedn.parent);
newdn.parent = this;
DaxeNode dn = firstChild;
if (dn == beforedn) {
DaxeNode save = firstChild;
firstChild = newdn;
newdn.nextSibling = save;
} else {
while (dn != null && dn.nextSibling != beforedn) {
dn = dn.nextSibling;
}
assert(dn != null);
assert(dn.nextSibling == beforedn);
DaxeNode save = dn.nextSibling;
dn.nextSibling = newdn;
newdn.nextSibling = save;
}
}
void insertAfter(DaxeNode newdn, DaxeNode afterdn) {
assert(this == afterdn.parent);
if (afterdn.nextSibling == null)
appendChild(newdn);
else
insertBefore(newdn, afterdn.nextSibling);
}
void removeChild(DaxeNode dn) {
if (dn.previousSibling != null)
dn.previousSibling.nextSibling = dn.nextSibling;
if (dn == firstChild)
firstChild = dn.nextSibling;
dn.parent = null;
dn.nextSibling = null;
}
/**
* Replaces this node by the given node (in the tree).
*/
void replaceWith(DaxeNode dn) {
if (parent.firstChild == this)
parent.firstChild = dn;
else
previousSibling.nextSibling = dn;
dn.parent = parent;
dn.nextSibling = nextSibling;
parent = null;
nextSibling = null;
}
/**
* Merges adjacent child text nodes.
*/
void normalize() {
for (DaxeNode dn=firstChild; dn != null; dn=dn.nextSibling) {
while (dn.nodeType == TEXT_NODE && dn.nextSibling != null &&
dn.nextSibling.nodeType == TEXT_NODE) {
dn.nodeValue = "${dn.nodeValue}${dn.nextSibling.nodeValue}";
removeChild(dn.nextSibling);
}
}
}
void remove(Position pos, int length) {
if (nodeType == ELEMENT_NODE) {
for (int i=pos.dnOffset; i<length; i++) {
removeChild(childAtOffset(pos.dnOffset));
}
} else {
String v = nodeValue;
String s1 = v.substring(0, pos.dnOffset);
String s2 = v.substring(pos.dnOffset + length);
nodeValue = "$s1$s2";
}
}
/**
* Add a newline after this element when serializing.
* If it returns true, fixLineBreaks() is usually called in the fromNode constructor.
*/
bool newlineAfter() {
return(false);
}
/**
* Add a newline after the start tag and if necessary a newline before the end tag
* in this element when serializing.
* If it returns true, fixLineBreaks() is usually called in the fromNode constructor.
*/
bool newlineInside() {
return(false);
}
/**
* Remove newlines that will be added at serialization.
*/
void fixLineBreaks() {
if (newlineInside() && firstChild != null && firstChild is DNText) {
String s = firstChild.nodeValue;
if (s.startsWith('\n')) {
if (s.length == 1)
removeChild(firstChild);
else
firstChild.nodeValue = s.substring(1);
}
}
DaxeNode lastNotText = lastChild;
while (lastNotText != null && lastNotText is DNText)
lastNotText = lastNotText.previousSibling;
if (newlineInside() && lastChild is DNText && (lastNotText == null || !lastNotText.newlineAfter())) {
String s = lastChild.nodeValue;
if (s.endsWith('\n')) {
if (s.length == 1)
removeChild(lastChild);
else
lastChild.nodeValue = s.substring(0, s.length - 1);
}
}
for (DaxeNode dn=firstChild; dn != null; dn=dn.nextSibling) {
if (dn.newlineAfter() && dn.nextSibling is DNText) {
String s = dn.nextSibling.nodeValue;
if (s.startsWith('\n')) {
if (s.length == 1)
removeChild(dn.nextSibling);
else
dn.nextSibling.nodeValue = s.substring(1);
}
}
}
}
/**
* DOM serialization. Can be overriden in DaxeNode subclasses.
*/
x.Node toDOMNode(x.Document domDocument) {
assert(nodeType == ELEMENT_NODE); // the other types are handled in subclasses DNDocument and DNText
x.Element el = domDocument.createElementNS(namespaceURI, nodeName);
for (DaxeAttr att in attributes)
el.setAttributeNS(att.namespaceURI, att.name, att.value);
if (newlineInside() || firstChild != null) {
if (newlineInside())
el.appendChild(domDocument.createTextNode('\n'));
for (DaxeNode dn=firstChild; dn != null; dn=dn.nextSibling) {
el.appendChild(dn.toDOMNode(domDocument));
if (dn.newlineAfter())
el.appendChild(domDocument.createTextNode('\n'));
}
DaxeNode lastNotText = lastChild;
while (lastNotText != null && lastNotText is DNText)
lastNotText = lastNotText.previousSibling;
if (newlineInside() && lastChild != null && (lastNotText == null || !lastNotText.newlineAfter()))
el.appendChild(domDocument.createTextNode('\n'));
}
return(el);
}
/**
* XML serialization. Based on DOM serialization (see [toDOMNode]);
*/
String toString() {
x.DOMImplementation domimpl = new x.DOMImplementationImpl();
x.Document domdoc = domimpl.createDocument(null, null, null);
x.Node n = toDOMNode(domdoc);
return(n.toString());
}
/// escapes XML character entities for serialization
static String escape(String s) {
s = s.replaceAll('&', '&');
s = s.replaceAll('"', '"');
//s = s.replaceAll("'", ''');
s = s.replaceAll('<', '<');
s = s.replaceAll('>', '>');
return(s);
}
void updateValidity() {
valid = doc.cfg.elementIsValid(this);
h.Element hel = getHTMLNode();
if (hel == null)
return;
if (valid && hel.classes.contains('invalid'))
hel.classes.remove('invalid');
else if (!valid && !hel.classes.contains('invalid'))
hel.classes.add('invalid');
}
/**
* This method is called when the user creates a new node, before it is inserted.
* By default, it displays the attribute dialog when there are attributes.
*/
void newNodeCreationUI(ActionFunction okfct) {
if (ref != null && doc.cfg.elementAttributes(ref).length > 0)
attributeDialog(() => okfct());
else
okfct();
}
void attributeDialog([ActionFunction okfct]) {
if (ref != null) {
AttributeDialog dlg = new AttributeDialog(this, okfct);
dlg.show();
} else {
UnknownElementDialog dlg = new UnknownElementDialog(this, okfct);
dlg.show();
}
}
Position findPosition(num x, num y) {
// we assume the click was in this element
/*
I wish I could use Range, like this:
if (document.caretRangeFromPoint) {
range = document.caretRangeFromPoint(e.pageX, e.pageY);
} else if (e.rangeParent) {
range = document.createRange();
range.setStart(e.rangeParent, e.rangeOffset);
}
but caretRangeFromPoint does not work in Firefox, and rangeParent/rangeOffset
do not exist in Dart...
cf http://stackoverflow.com/q/3189812/438970
and http://code.google.com/p/dart/issues/detail?id=9227
and http://code.google.com/p/dart/issues/detail?id=11723
*/
Position pos = new Position(this, 0);
if (nodeType == ELEMENT_NODE || nodeType == DOCUMENT_NODE) {
for (DaxeNode dn=firstChild; dn != null; dn=dn.nextSibling) {
// hnx1, hny1, lineHeight: first char in HTML element
// hnx2, hny2, lineHeight: last char
h.Element hn = dn.getHTMLNode();
if (hn == null)
continue;
double hnx1, hny1, hnx2, hny2;
double topLineHeight, bottomLineHeight;
// NOTE: the main problem here is to avoid adding spans to find the position
if (hn is h.DivElement && hn.nodes.length > 0 &&
hn.firstChild is h.SpanElement && (hn.firstChild as h.SpanElement).classes.contains('start_tag') &&
hn.lastChild is h.SpanElement&& (hn.lastChild as h.SpanElement).classes.contains('end_tag')) {
// the spans are tags
h.Element span_test = hn.firstChild;
h.Rectangle box = span_test.getBoundingClientRect();
hnx1 = box.left;
hny1 = box.top;
topLineHeight = span_test.offset.height.toDouble();
span_test = hn.lastChild;
box = span_test.getBoundingClientRect();
hnx2 = box.right;
hny2 = box.bottom;
bottomLineHeight = span_test.offset.height.toDouble();
} else if (hn is h.DivElement || hn is h.TableCellElement || hn is h.TableRowElement ||
hn is h.TableElement || hn is h.ImageElement || hn.classes.contains('form')) {
// block
// for DivElement: no span to tag the div: we take the entire div into account
h.Rectangle box = hn.getBoundingClientRect();
// FIXME: box is not good for a tr containing a td using rowspan
hnx1 = box.left;
hny1 = box.top;
if (hn.classes.contains('form')) // FIXME: this is a hack !
hnx2 = hn.querySelector('table').getBoundingClientRect().right;
else
hnx2 = box.right;
hny2 = box.bottom;
topLineHeight = bottomLineHeight = box.height;
} else if (hn is h.SpanElement && hn.nodes.length == 1 && hn.firstChild is h.Text &&
!hn.firstChild.nodeValue.endsWith('\n')) {
// text node that does not end with \n
List<h.Rectangle> rects = hn.getClientRects();
if (rects.length == 0)
return(null);
h.Rectangle box = rects.first;
hnx1 = box.left;
hny1 = box.top;
topLineHeight = box.height * 1.4;
box = rects.last;
hnx2 = box.right;
hny2 = box.bottom;
bottomLineHeight = box.height * 1.4;
} else if (hn.firstChild is h.Element && hn.lastChild is h.SpanElement &&
hn.lastChild.lastChild is h.Text &&
!hn.lastChild.lastChild.nodeValue.endsWith('\n')) {
// span with a text node at the end which does not end with \n
// note: possibles selections on the text make the tests a bit complex...
h.Element span_test = hn.firstChild;
List<h.Rectangle> rects = span_test.getClientRects();
if (rects.length == 0)
return(null);
h.Rectangle box = rects.first;
hnx1 = box.left;
hny1 = box.top;
topLineHeight = box.height * 1.3;
span_test = hn.lastChild;
rects = span_test.getClientRects();
if (rects.length == 0)
return(null);
box = rects.last;
hnx2 = box.right;
hny2 = box.bottom;
bottomLineHeight = box.height * 1.3;
} else {
// NOTE: for a span, getBoundingClientRect and getClientRects return a wrong bottom when
// there are \n inside (except maybe with IE), so we can't even use them on hn to avoid
// appending spans (and bec. it depends on browsers we can't just add a \n).
// Using an empty span does not work.
// FIXME: adding a span with text can change a table layout with Firefox,
// causing wrong results and side effects
// -> TODO: test WORD JOINER ("\u2060") instead of "|"
h.Element span_test = new h.Element.tag('span');
span_test.append(new h.Text("|"));
if (hn.nodes.length == 1 && hn.firstChild is h.BRElement) {
hn.append(span_test);
h.Rectangle box = span_test.getBoundingClientRect();
hnx1 = -1.0;
hny1 = box.top;
topLineHeight = bottomLineHeight = span_test.offset.height.toDouble() * 1.4;
hnx2 = -1.0;
hny2 = box.bottom;
span_test.remove();
} else {
if (hn.nodes.isEmpty)
hn.append(span_test);
else
hn.insertBefore(span_test, hn.firstChild);
h.Rectangle box = span_test.getBoundingClientRect();
hnx1 = box.left;
hny1 = box.top;
// note: maybe we should use CSS line-height here, but it is hard to get the value
topLineHeight = span_test.offset.height.toDouble() * 1.4;
span_test.remove();
if (hn is h.LIElement) {
h.Node lastDescendant = hn;
while (lastDescendant.firstChild != null &&
(lastDescendant.lastChild is! h.Text ||
(lastDescendant.lastChild.nodeValue == '\n' && lastDescendant.lastChild.previousNode != null)) &&
lastDescendant.lastChild is! h.ImageElement) {
if (lastDescendant.lastChild is h.Text && lastDescendant.lastChild.nodeValue == '\n' &&
lastDescendant.lastChild.previousNode != null)
lastDescendant = lastDescendant.lastChild.previousNode; // case of a hidden paragraph or block inside li
else
lastDescendant = lastDescendant.lastChild;
}
lastDescendant.append(span_test);
} else
hn.append(span_test);
box = span_test.getBoundingClientRect();
hnx2 = box.left;
hny2 = box.bottom;
bottomLineHeight = span_test.offset.height.toDouble() * 1.4;
span_test.remove();
}
}
if ((y < hny1 + topLineHeight && (y < hny1 - 1 || (x < hnx1 + 1 && hn is! h.LIElement &&
dn is! DNHiddenP)))) {
// position is before this child
return(pos);
}
if (y > hny2 - bottomLineHeight && (y > hny2 + 1 || (x > hnx2 - 1 && hn is! h.LIElement))) {
// position is after this child
pos = new Position(this, offsetOf(dn) + 1);
} else {
// position is within this child
pos = dn.findPosition(x, y);
return(pos);
}
}
} else if (nodeType == TEXT_NODE) {
/* doesn't work with other browsers than Chrome...
h.Range range = h.document.$dom_caretRangeFromPoint(x, y);
h.Element hn = getHTMLNode();
if (range.startContainer == hn || range.startContainer == hn.$dom_firstChild)
return(new Position(this, range.startOffset));
*/
int pp = 0; // position in the XML node
for (h.Node hn in getHTMLNode().nodes) {
h.Text ht;
if (hn is h.Text)
ht = hn;
else if (hn is h.Element) // selection span
ht = hn.firstChild;
else
continue;
h.Range range = new h.Range();
for (int i=0; i<ht.length; i++) {
// problem: depending on the browser, the clientrects are not the same
// for \n or for line-breaking space characters (or for the character before a \n with IE11)...
if (nodeValue[pp + i] != '\n') {
range.setStart(ht, i);
range.setEnd(ht, i+1);
List<h.Rectangle> rects = range.getClientRects();
for (h.Rectangle r in rects) {
if (h.window.navigator.appVersion.contains("Trident") && pp + i + 1 < nodeValue.length &&
nodeValue[pp + i + 1] == '\n' && r.width == 0) {
// With IE11, ignore a 0-width rect for the character before a \n
continue;
}
if (i < nodeValue.length - 1 && r.left == r.right &&
x < r.left && y < r.bottom) {
//print("left of line start after newline");
return(new Position(this, pp + i+1));
} if (x < r.right && y <= r.bottom) {
if (x < (r.left + r.right) / 2)
return(new Position(this, pp + i));
else
return(new Position(this, pp + i+1));
} else if (y < r.top - 5) {
//print("line above");
// the point is on the line above
if (pp+i == 0 || nodeValue[pp+i] == ' ')
return(new Position(this, pp + i));
else
return(new Position(this, pp + i - 1));
}
}
} else {
// ranges are not reliable for positions of newline characters
// FIXME: adding text can change a table layout with Firefox, causing wrong results
String s = ht.text;
ht.text = "${s.substring(0, i)}|${s.substring(i)}";
range.setStart(ht, i);
range.setEnd(ht, i+1);
List<h.Rectangle> rects = range.getClientRects();
ht.text = s;
if (h.window.navigator.appVersion.contains("Trident")) {
// tested on IE11
if (y <= rects.first.bottom) {
// before the line bottom
return(new Position(this, pp + i));
}
} else {
// tested on Chromium and Firefox
for (h.Rectangle r in rects) {
if (y <= r.bottom) {
// before the line bottom
return(new Position(this, pp + i));
}
}
}
}
}
pp += ht.length;
}
// not found...
//print("position not found");
}
return(new Position(this, offsetLength));
}
Position firstCursorPositionInside() {
return(new Position(this, 0));
}
Position lastCursorPositionInside() {
return(new Position(this, offsetLength));
}
void setStyle(h.Element hn) {
String styleParam = doc.cfg.elementParameterValue(ref, 'style', null);
if (styleParam != null) {
List<String> styleList = styleParam.split(';');
for (String style in styleList) {
if (style == STYLE_BOLD) {
hn.style.fontWeight = 'bold';
} else if (style == STYLE_ITALIC) {
hn.style.fontStyle = 'italic';
} else if (style == STYLE_SUPERSCRIPT) {
hn.style.verticalAlign = 'super';
hn.style.fontSize = '80%';
} else if (style == STYLE_SUBSCRIPT) {
hn.style.verticalAlign = 'sub';
hn.style.fontSize = '80%';
} else if (style == STYLE_UNDERLINE) {
hn.style.textDecoration = 'underline';
} else if (style == STYLE_STRIKETHROUGH) {
hn.style.textDecoration = 'line-through';
} else if (style.startsWith(STYLE_BACKGROUND_COLOR)) {
hn.style.background = _getColor(style);
} else if (style.startsWith(STYLE_FOREGROUND_COLOR)) {
hn.style.color = _getColor(style);
}
}
}
String fontParam = doc.cfg.elementParameterValue(ref, 'police', null);
if (fontParam != null) {
if (fontParam == 'Monospaced')
fontParam = 'monospace';
hn.style.fontFamily = fontParam;
}
String sizeParam = doc.cfg.elementParameterValue(ref, 'taille', null);
if (sizeParam != null) {
hn.style.fontSize = sizeParam;
}
}
String _getColor(String style) {
Iterable<Match> matches = COLOR_PATTERN.allMatches(style);
for (Match m in matches) {
final List<int> color = new List<int>();
for (int j = 0; j < 3; j++) {
String value = m.group(j + 1);
if (value.startsWith("x"))
color[j] = int.parse(value.substring(1), radix: 16);
else
color[j] = int.parse(value);
}
return("rgb(${color[0]}, ${color[1]}, ${color[2]})");
}
return(null);
}
/**
* Calls afterInsert for this node and all its descendants.
*/
void callAfterInsert() {
afterInsert();
for (DaxeNode dn=firstChild; dn != null; dn=dn.nextSibling) {
dn.callAfterInsert();
}
}
/**
* Calls beforeRemove for this node and all its descendants.
*/
void callBeforeRemove() {
beforeRemove();
for (DaxeNode dn=firstChild; dn != null; dn=dn.nextSibling) {
dn.callBeforeRemove();
}
}
/**
* Called after this node was inserted. Does nothing by default.
*/
void afterInsert() {
}
/**
* Called before this node is removed. Does nothing by default.
*/
void beforeRemove() {
}
/**
* Returns true if the children should be using ParentUpdatingDNText instead of DNText
*/
bool get needsParentUpdatingDNText {
return false;
}
}
Index: modules/damieng/graphical_editor/daxe/lib/src/file_open_dialog.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/file_open_dialog.dart
/*
This file is part of Daxe.
Daxe is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Daxe is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Daxe. If not, see <http://www.gnu.org/licenses/>.
*/
part of daxe;
/**
* This class is useless, it does not return a real path on the disk.
*/
@deprecated
class FileOpenDialog {
ActionFunction okfct;
h.File file;
FileOpenDialog(this.okfct) {
}
void show() {
h.DivElement div1 = new h.DivElement();
div1.id = 'dlg1';
div1.classes.add('dlg1');
h.DivElement div2 = new h.DivElement();
div2.classes.add('dlg2');
h.DivElement div3 = new h.DivElement();
div3.classes.add('dlg3');
h.FormElement form = new h.FormElement();
h.FileUploadInputElement input = new h.FileUploadInputElement();
input.id = 'file_input';
input.onChange.listen((h.Event event) => onChange(event));
form.append(input);
h.DivElement div_buttons = new h.DivElement();
div_buttons.classes.add('buttons');
h.ButtonElement bCancel = new h.ButtonElement();
bCancel.attributes['type'] = 'button';
bCancel.appendText(Strings.get("button.Cancel"));
bCancel.onClick.listen((h.MouseEvent event) => div1.remove());
div_buttons.append(bCancel);
h.ButtonElement bOk = new h.ButtonElement();
bOk.id = 'file_ok';
bOk.disabled = true;
bOk.attributes['type'] = 'submit';
bOk.appendText(Strings.get("button.OK"));
bOk.onClick.listen((h.MouseEvent event) => ok(event));
div_buttons.append(bOk);
form.append(div_buttons);
div3.append(form);
div2.append(div3);
div1.append(div2);
h.document.body.append(div1);
}
void onChange(h.Event event) {
h.ButtonElement bOk = h.query('button#file_ok');
bOk.disabled = false;
}
void ok(h.MouseEvent event) {
event.preventDefault();
h.FileUploadInputElement input = h.query('input#file_input');
List<h.File> files = input.files;
if (files.length > 0)
file = files[0];
h.query('div#dlg1').remove();
okfct();
}
h.File getFile() {
return(file);
}
}
Index: modules/damieng/graphical_editor/daxe/lib/src/find_dialog.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/find_dialog.dart
/*
This file is part of Daxe.
Daxe is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Daxe is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Daxe. If not, see <http://www.gnu.org/licenses/>.
*/
part of daxe;
/**
* Find and Replace dialog.
*/
class FindDialog {
static bool caseSensitive = false;
static bool backwards = false;
static String findString = '';
void show() {
h.DivElement div_find = h.document.getElementById('find_dlg');
if (div_find != null) {
h.TextInputElement inputFind = h.document.getElementById('find_dlg_find_field');
inputFind.focus();
return;
}
h.Element divdoc = h.querySelector("#doc1");
divdoc.style.bottom = '10.5em';
div_find = new h.DivElement();
div_find.id = 'find_dlg';
div_find.classes.add('find');
h.FormElement form = new h.FormElement();
h.TableElement table = new h.TableElement();
h.TableRowElement tr = new h.TableRowElement();
h.TableCellElement td = new h.TableCellElement();
td.text = Strings.get('find.find');
tr.append(td);
td = new h.TableCellElement();
h.TextInputElement inputFind = new h.TextInputElement();
inputFind
..id = 'find_dlg_find_field'
..name = 'find'
..size = 40
..value = findString;
td.append(inputFind);
tr.append(td);
table.append(tr);
tr = new h.TableRowElement();
td = new h.TableCellElement();
td.text = Strings.get('find.replace_by');
tr.append(td);
td = new h.TableCellElement();
h.TextInputElement inputReplace = new h.TextInputElement();
inputReplace
..id = 'find_dlg_replace_field'
..name = 'replace_by'
..size = 40;
td.append(inputReplace);
tr.append(td);
table.append(tr);
div_find.append(table);
h.DivElement div_options = new h.DivElement();
div_options.classes.add('options');
h.CheckboxInputElement cbCaseSensitive = new h.CheckboxInputElement();
cbCaseSensitive
..id = 'find_cb_ignore_case'
..checked = caseSensitive
..onChange.listen((h.Event event) => caseSensitive = cbCaseSensitive.checked);
div_options.append(cbCaseSensitive);
h.LabelElement labelIgnoreCase = new h.LabelElement();
labelIgnoreCase
..htmlFor = 'find_cb_ignore_case'
..text = Strings.get("find.case_sensitive");
div_options.append(labelIgnoreCase);
h.CheckboxInputElement cbBackwards = new h.CheckboxInputElement();
cbBackwards
..id = 'find_cb_backwards'
..checked = backwards
..onChange.listen((h.Event event) => backwards = cbBackwards.checked);
div_options.append(cbBackwards);
h.LabelElement labelBackwards = new h.LabelElement();
labelBackwards
..htmlFor = 'find_cb_backwards'
..text = Strings.get("find.backwards");
div_options.append(labelBackwards);
// TODO: option to look at attribute values, XPath search
form.append(div_options);
h.DivElement div_buttons = new h.DivElement();
div_buttons.classes.add('buttons');
h.ButtonElement bClose = new h.ButtonElement();
bClose
..attributes['type'] = 'button'
..appendText(Strings.get("button.Close"))
..onClick.listen((h.MouseEvent event) => close());
div_buttons.append(bClose);
h.ButtonElement bReplace = new h.ButtonElement();
bReplace
..attributes['type'] = 'button'
..appendText(Strings.get("find.replace"))
..onClick.listen((h.MouseEvent event) => replace());
div_buttons.append(bReplace);
h.ButtonElement bReplaceFind = new h.ButtonElement();
bReplaceFind
..attributes['type'] = 'button'
..appendText(Strings.get("find.replace_find"))
..onClick.listen((h.MouseEvent event) => replaceFind());
div_buttons.append(bReplaceFind);
h.ButtonElement bReplaceAll = new h.ButtonElement();
bReplaceAll
..attributes['type'] = 'button'
..appendText(Strings.get("find.replace_all"))
..onClick.listen((h.MouseEvent event) => replaceAll());
div_buttons.append(bReplaceAll);
h.ButtonElement bNext = new h.ButtonElement();
bNext
..attributes['type'] = 'button'
..appendText(Strings.get("find.next"))
..onClick.listen((h.MouseEvent event) => next());
div_buttons.append(bNext);
form.append(div_buttons);
div_find.append(form);
h.document.body.append(div_find);
div_find.onKeyDown.listen((h.KeyboardEvent event) {
if (event.keyCode == h.KeyCode.ESC) {
close();
}
});
inputFind.focus();
}
void next() {
//FIXME: does not work with DNForm and DNSimpleType: selection is not visible
// (but then, how could we select a part of a select element anyway ?)
findString = ((h.document.getElementById('find_dlg_find_field')) as h.TextInputElement).value;
if (findString == '')
return;
Position pos;
if (!backwards) {
Position end = page.getSelectionEnd();
if (end == null)
end = new Position(doc.dndoc, 0);
pos = nextAt(end);
} else {
Position start = page.getSelectionStart();
if (start == null)
start = new Position(doc.dndoc, doc.dndoc.offsetLength);
pos = previousAt(start);
}
if (pos != null) {
page.moveCursorTo(pos);
page.cursor.setSelection(pos, new Position(pos.dn, pos.dnOffset + findString.length));
}
}
Position nextAt(Position pos) {
DaxeNode parent = pos.dn;
int offset = pos.dnOffset;
if (parent is! DNText) {
parent = parent.childAtOffset(offset);
offset = 0;
}
while (parent != null) {
if (parent is DNText) {
int index;
if (!caseSensitive)
index = parent.nodeValue.toLowerCase().indexOf(findString.toLowerCase(), offset);
else
index = parent.nodeValue.indexOf(findString, offset);
if (index != -1)
return(new Position(parent, index));
}
parent = parent.nextNode();
offset = 0;
}
return(null);
}
Position previousAt(Position pos) {
DaxeNode parent = pos.dn;
int offset = pos.dnOffset;
if (parent is! DNText) {
if (offset > 0)
parent = parent.childAtOffset(offset-1);
else {
DaxeNode p = parent;
parent = null;
while (p != null) {
if (p.previousSibling != null) {
parent = p.previousSibling;
break;
}
p = p.parent;
}
}
if (parent != null)
offset = parent.offsetLength;
}
while (parent != null) {
if (parent is DNText) {
int index;
if (!caseSensitive)
index = parent.nodeValue.toLowerCase().substring(0, offset).lastIndexOf(findString.toLowerCase());
else
index = parent.nodeValue.substring(0, offset).lastIndexOf(findString);
if (index != -1)
return(new Position(parent, index));
}
parent = parent.previousNode();
if (parent != null)
offset = parent.offsetLength;
}
return(null);
}
void replace() {
Position start = new Position.clone(page.getSelectionStart());
Position end = new Position.clone(page.getSelectionEnd());
if (start == null || start.dn is! DNText)
return;
String replaceString = ((h.document.getElementById('find_dlg_replace_field')) as h.TextInputElement).value;
UndoableEdit edit = new UndoableEdit.compound(Strings.get('find.replace'));
// we should not remove the whole string before inserting the new one, because this might
// cause the text node referenced by pos to disappear, and the insert won't work
// -> we do the insert first to make sure not to remove a text node
if (replaceString != '')
edit.addSubEdit(new UndoableEdit.insertString(end, replaceString));
if (start != end && start.dn == end.dn)
edit.addSubEdit(new UndoableEdit.removeString(start, end.dnOffset - start.dnOffset));
doc.doNewEdit(edit);
page.cursor.setSelection(start, new Position(start.dn, start.dnOffset + replaceString.length));
}
void replaceFind() {
replace();
next();
}
void replaceAll() {
findString = ((h.document.getElementById('find_dlg_find_field')) as h.TextInputElement).value;
if (findString == '')
return;
String replaceString = ((h.document.getElementById('find_dlg_replace_field')) as h.TextInputElement).value;
Position pos = previousAt(new Position(doc.dndoc, doc.dndoc.offsetLength));
// we are going backwards in order to be able to combine edits
UndoableEdit edit = new UndoableEdit.compound(Strings.get('find.replace_all'));
while (pos != null) {
if (replaceString != '')
edit.addSubEdit(new UndoableEdit.insertString(new Position(pos.dn, pos.dnOffset + findString.length),
replaceString));
edit.addSubEdit(new UndoableEdit.removeString(pos, findString.length));
pos = previousAt(pos);
}
doc.doNewEdit(edit);
}
void close() {
h.DivElement div_find = h.document.getElementById('find_dlg');
div_find.remove();
h.Element divdoc = h.querySelector("#doc1");
divdoc.style.bottom = '1.5em';
page.focusCursor();
}
}
Index: modules/damieng/graphical_editor/daxe/lib/src/help_dialog.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/help_dialog.dart
/*
This file is part of Daxe.
Daxe is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Daxe is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Daxe. If not, see <http://www.gnu.org/licenses/>.
*/
part of daxe;
/**
* Help dialog for an element or an attribute.
*/
class HelpDialog {
x.Element elementRef;
x.Element attributeRef;
StreamSubscription<h.KeyboardEvent> keyboardSubscription;
HelpDialog.Element(this.elementRef) {
}
HelpDialog.Attribute(this.attributeRef, this.elementRef) {
}
void show() {
h.DivElement div1 = new h.DivElement();
div1.id = 'dlg1';
div1.classes.add('dlg1');
h.DivElement div2 = new h.DivElement();
div2.classes.add('dlg2');
h.DivElement div3 = new h.DivElement();
div3.classes.add('dlg3');
// top-right close button
h.DivElement topDiv = new h.DivElement();
topDiv.style.position = 'absolute';
topDiv.style.top = '0px';
topDiv.style.right = '0px';
topDiv.style.width = '16px';
topDiv.style.height = '16px';
h.ImageElement img = new h.ImageElement();
img.src = 'packages/daxe/images/close_dialog.png';
img.width = 16;
img.height = 16;
img.style.position = 'fixed';
img.onClick.listen((h.MouseEvent event) => close());
topDiv.append(img);
div3.append(topDiv);
h.DivElement title = new h.DivElement();
title.classes.add('dlgtitle');
if (attributeRef == null)
title.text = doc.cfg.elementTitle(elementRef);
else
title.text = doc.cfg.attributeTitle(elementRef, attributeRef);
div3.append(title);
if (attributeRef == null) {
h.ParagraphElement p = new h.ParagraphElement();
p.appendText(Strings.get('help.element_name') + ' ');
h.SpanElement nameSpan = new h.SpanElement();
nameSpan.classes.add('help_element_name');
nameSpan.text = doc.cfg.elementName(elementRef);
p.append(nameSpan);
div3.append(p);
}
String documentation;
if (attributeRef == null)
documentation = doc.cfg.documentation(elementRef);
else
documentation = doc.cfg.attributeDocumentation(elementRef, attributeRef);
if (documentation != null) {
documentation = Config.formatDoc(documentation);
h.ParagraphElement p = new h.Element.html("<p>$documentation</p>");
div3.append(p);
}
if (attributeRef == null) {
String regexp = doc.cfg.regularExpression(elementRef);
if (regexp != null) {
h.DivElement divRegExp = new h.DivElement();
divRegExp.classes.add('help_regexp');
divRegExp.text = regexp;
div3.append(divRegExp);
}
h.SpanElement titleParents = new h.SpanElement();
titleParents.id = 'help_parents';
titleParents.classes.add('help_list_title');
titleParents.text = Strings.get('help.parents');
titleParents.onClick.listen((h.MouseEvent event) => fillParents());
div3.append(titleParents);
h.SpanElement titleChildren = new h.SpanElement();
titleChildren.id = 'help_children';
titleChildren.classes.add('help_list_title');
titleChildren.text = Strings.get('help.children');
titleChildren.onClick.listen((h.MouseEvent event) => fillChildren());
div3.append(titleChildren);
h.SpanElement titleAttributes = new h.SpanElement();
titleAttributes.id = 'help_attributes';
titleAttributes.classes.add('help_list_title');
titleAttributes.text = Strings.get('help.attributes');
titleAttributes.onClick.listen((h.MouseEvent event) => fillAttributes());
div3.append(titleAttributes);
h.DivElement divList = new h.DivElement();
divList.classes.add('help_list_div');
h.UListElement ul = new h.UListElement();
ul.id = 'help_list';
divList.append(ul);
div3.append(divList);
}
h.DivElement div_buttons = new h.DivElement();
div_buttons.classes.add('buttons');
h.ButtonElement bOk = new h.ButtonElement();
bOk.attributes['type'] = 'submit';
bOk.appendText(Strings.get("button.Close"));
bOk.onClick.listen((h.MouseEvent event) => close());
div_buttons.append(bOk);
div3.append(div_buttons);
div2.append(div3);
div1.append(div2);
h.document.body.append(div1);
if (div3.clientHeight > div1.clientHeight * 3 / 4) {
// enlarge dialog width for large content models
div2.style.left = "33%";
div3.style.left = "-25%";
}
if (attributeRef == null)
fillChildren();
keyboardSubscription = h.document.onKeyDown.listen(null);
keyboardSubscription.onData((h.KeyboardEvent event) {
if (event.keyCode == h.KeyCode.ESC) {
close();
}
});
bOk.focus();
}
void fillParents() {
h.SpanElement titleParents = h.document.getElementById('help_parents');
titleParents.classes.add('selected_tab');
h.SpanElement titleChildren = h.document.getElementById('help_children');
titleChildren.classes.remove('selected_tab');
h.SpanElement titleAttributes = h.document.getElementById('help_attributes');
titleAttributes.classes.remove('selected_tab');
h.UListElement ul = h.document.getElementById('help_list');
ul.nodes.clear();
List<x.Element> parents = doc.cfg.parentElements(elementRef);
if (parents == null || parents.length == 0)
return;
HashMap<x.Element, String> titleMap = bestTitles(parents.toSet());
parents.sort((ref1, ref2) => titleMap[ref1].toLowerCase().compareTo(
titleMap[ref2].toLowerCase()));
for (x.Element parentRef in parents) {
h.LIElement li = new h.LIElement();
li.text = titleMap[parentRef];
String documentation = doc.cfg.documentation(parentRef);
if (documentation != null)
li.title = documentation;
li.onClick.listen((h.MouseEvent event) => switchToElement(parentRef));
li.classes.add('help_selectable');
ul.append(li);
}
}
/**
* Look for titles with additional info when several elements have the same name.
*/
HashMap<x.Element, String> bestTitles(Set<x.Element> refs, [int level=0]) {
HashMap<String, Set<x.Element>> titleSets = new HashMap<String, Set<x.Element>>();
// create the map of titles including the element having a common ancestor title at given level
for (x.Element ref in refs) {
Set<x.Element> ancestors = ancestorsAtLevel(ref, level);
// check if ancestors have the same title and if so add title
String ancestorTitle = doc.cfg.elementTitle(ancestors.first);
if (ancestorTitle != null) {
bool sameTitle = true;
for (x.Element ancestor in ancestors) {
if (doc.cfg.elementTitle(ancestor) != ancestorTitle) {
sameTitle = false;
break;
}
}
if (sameTitle) {
String title;
if (level == 0)
title = doc.cfg.elementTitle(ref);
else
title = doc.cfg.elementTitle(ref) + " (" + ancestorTitle + ")";
Set set = titleSets[title];
if (set == null) {
set = new HashSet<x.Element>();
titleSets[title] = set;
}
set.add(ref);
}
}
}
// go to the next level for elements with the same title
// and create the result map.
HashMap<x.Element, String> resMap = new HashMap<x.Element, String>();
for (String title in titleSets.keys) {
Set<x.Element> set = titleSets[title];
if (set.length > 1 && level < 10) {
HashMap<x.Element, String> map2 = bestTitles(set, level+1);
resMap.addAll(map2);
for (x.Element ref in set)
if (map2[ref] == null) {
// could not find a unique title for this one, adding the type could be useful...
if (title.indexOf('(') == -1 && ref.getAttribute('type') != '')
resMap[ref] = title + " (" + ref.getAttribute('type') + ")";
else
resMap[ref] = title;
}
} else {
for (x.Element ref in set)
resMap[ref] = title;
}
}
return(resMap);
}
Set<x.Element> ancestorsAtLevel(x.Element ref, int level) {
Set<x.Element> ancestors = new Set<x.Element>();
ancestors.add(ref);
for (int i=0; i<level; i++) {
Set<x.Element> ancestors2 = new Set<x.Element>();
for (x.Element ref2 in ancestors) {
List<x.Element> parentList = doc.cfg.parentElements(ref2);
if (parentList != null)
ancestors2.addAll(parentList);
}
ancestors = ancestors2;
}
return(ancestors);
}
void fillChildren() {
h.SpanElement titleParents = h.document.getElementById('help_parents');
titleParents.classes.remove('selected_tab');
h.SpanElement titleChildren = h.document.getElementById('help_children');
titleChildren.classes.add('selected_tab');
h.SpanElement titleAttributes = h.document.getElementById('help_attributes');
titleAttributes.classes.remove('selected_tab');
h.UListElement ul = h.document.getElementById('help_list');
ul.nodes.clear();
List<x.Element> children = doc.cfg.subElements(elementRef);
if (children == null || children.length == 0)
return;
HashMap<x.Element, String> titleMap = new HashMap.fromIterable(children,
value:(x.Element ref) => doc.cfg.elementTitle(ref));
children.sort((ref1, ref2) => titleMap[ref1].toLowerCase().compareTo(
titleMap[ref2].toLowerCase()));
for (x.Element childRef in children) {
h.LIElement li = new h.LIElement();
li.text = titleMap[childRef];
String documentation = doc.cfg.documentation(childRef);
if (documentation != null)
li.title = documentation;
li.onClick.listen((h.MouseEvent event) => switchToElement(childRef));
li.classes.add('help_selectable');
ul.append(li);
}
}
void fillAttributes() {
h.SpanElement titleParents = h.document.getElementById('help_parents');
titleParents.classes.remove('selected_tab');
h.SpanElement titleChildren = h.document.getElementById('help_children');
titleChildren.classes.remove('selected_tab');
h.SpanElement titleAttributes = h.document.getElementById('help_attributes');
titleAttributes.classes.add('selected_tab');
h.UListElement ul = h.document.getElementById('help_list');
ul.nodes.clear();
List<x.Element> attributes = doc.cfg.elementAttributes(elementRef);
if (attributes == null || attributes.length == 0)
return;
HashMap<x.Element, String> titleMap = new HashMap.fromIterable(attributes,
value:(x.Element attRef) => doc.cfg.attributeTitle(elementRef, attRef));
attributes.sort((ref1, ref2) => titleMap[ref1].toLowerCase().compareTo(
titleMap[ref2].toLowerCase()));
for (x.Element attRef in attributes) {
h.LIElement li = new h.LIElement();
li.text = titleMap[attRef];
String documentation = doc.cfg.attributeDocumentation(elementRef, attRef);
if (documentation != null)
li.title = documentation;
ul.append(li);
}
}
void switchToElement(x.Element elementRef) {
this.elementRef = elementRef;
attributeRef = null;
close();
show();
}
void close() {
keyboardSubscription.cancel();
h.DivElement div1 = h.document.getElementById('dlg1');
div1.remove();
page.focusCursor();
}
}
Index: modules/damieng/graphical_editor/daxe/lib/src/insert_panel.dart
+++ modules/damieng/graphical_editor/daxe/lib/src/insert_panel.dart
/*
This file is part of Daxe.
Daxe is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Daxe is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Daxe. If not, see <http://www.gnu.org/licenses/>.
*/
part of daxe;
/**
* Left panel to insert elements. It only displays the elements that can be inserted at
* the cursor position, according to the schema.
*/
class InsertPanel {
void update(DaxeNode parent, List<x.Element> refs, List<x.Element> validRefs) {
h.Element divInsert = h.document.getElementById('insert');
for (h.Element child in divInsert.children)
child.remove();
Config cfg = doc.cfg;
if (cfg == null)
return;
if (parent.nodeType == DaxeNode.ELEMENT_NODE && parent.ref != null) {
divInsert.append(_makeHelpButton(parent.ref));
String name = cfg.elementName(parent.ref);
h.SpanElement span = new h.SpanElement();
span.appendText(cfg.menuTitle(name));
divInsert.append(span);
divInsert.append(new h.HRElement());
}
List<x.Element> toolbarRefs;
if (page.toolbar != null)
toolbarRefs = page.toolbar.elementRefs();
else
toolbarRefs = null;
for (x.Element ref in refs) {
if (toolbarRefs != null && toolbarRefs.contains(ref))
continue;
if (doc.hiddenParaRefs != null && doc.hiddenParaRefs.contains(ref))
continue;
divInsert.append(_makeHelpButton(ref));
h.ButtonElement button = new h.ButtonElement();
button.attributes['type'] = 'button';
button.classes.add('insertb');
String name = cfg.elementName(ref);
button.value = name;
button.text = cfg.menuTitle(name);
if (!validRefs.contains(ref))
button.disabled = true;
button.onClick.listen((h.Event event) => insert(ref));
button.onKeyDown.listen((h.KeyboardEvent event) {
int keyCode = event.keyCode;
if (keyCode == h.KeyCode.ENTER) {
event.preventDefault();
insert(ref);
}
});
divInsert.append(button);
divInsert.append(new h.BRElement());
}
}
h.ButtonElement _makeHelpButton(x.Element ref) {
h.ButtonElement bHelp = new h.ButtonElement();
bHelp.attributes['type'] = 'button';
bHelp.classes.add('help');
bHelp.value = '?';
bHelp.text = '?';
String documentation = doc.cfg.documentation(ref);
if (documentation != null)
bHelp.title = documentation;
bHelp.onClick.listen((h.Event event) => help(ref));
return(bHelp);
}
void insert(x.Element ref) {
doc.insertNewNode(ref, 'element');
}
void help(x.Element ref) {