Commit 0fb37cee by cahrens

Replace with JS code.

parent 337d4947
...@@ -49,7 +49,6 @@ common/lib/xmodule/xmodule/js/src/conditional/display.js ...@@ -49,7 +49,6 @@ common/lib/xmodule/xmodule/js/src/conditional/display.js
common/lib/xmodule/xmodule/js/src/discussion/display.js common/lib/xmodule/xmodule/js/src/discussion/display.js
common/lib/xmodule/xmodule/js/src/html/display.js common/lib/xmodule/xmodule/js/src/html/display.js
common/lib/xmodule/xmodule/js/src/html/edit.js common/lib/xmodule/xmodule/js/src/html/edit.js
common/lib/xmodule/xmodule/js/src/problem/edit.js
common/lib/xmodule/xmodule/js/src/raw/edit/json.js common/lib/xmodule/xmodule/js/src/raw/edit/json.js
common/lib/xmodule/xmodule/js/src/raw/edit/metadata-only.js common/lib/xmodule/xmodule/js/src/raw/edit/metadata-only.js
common/lib/xmodule/xmodule/js/src/raw/edit/xml.js common/lib/xmodule/xmodule/js/src/raw/edit/xml.js
......
...@@ -154,7 +154,7 @@ class CapaDescriptor(CapaFields, RawDescriptor): ...@@ -154,7 +154,7 @@ class CapaDescriptor(CapaFields, RawDescriptor):
show_in_read_only_mode = True show_in_read_only_mode = True
template_dir_name = 'problem' template_dir_name = 'problem'
mako_template = "widgets/problem-edit.html" mako_template = "widgets/problem-edit.html"
js = {'coffee': [resource_string(__name__, 'js/src/problem/edit.coffee')]} js = {'js': [resource_string(__name__, 'js/src/problem/edit.js')]}
js_module_name = "MarkdownEditingDescriptor" js_module_name = "MarkdownEditingDescriptor"
has_author_view = True has_author_view = True
css = { css = {
......
# Coffeescript markdown support. // Generated by CoffeeScript 1.6.1
# Most of the functionality is in the markdownToXml function, (function() {
# which in fact is regular javascript within backticks. var _this = this,
__hasProp = {}.hasOwnProperty,
class @MarkdownEditingDescriptor extends XModule.Descriptor __extends = function(child, parent) {
# TODO really, these templates should come from or also feed the cheatsheet for (var key in parent) {
@multipleChoiceTemplate : "( ) #{gettext 'incorrect'}\n( ) #{gettext 'incorrect'}\n(x) #{gettext 'correct'}\n" if (__hasProp.call(parent, key)) child[key] = parent[key];
@checkboxChoiceTemplate: "[x] #{gettext 'correct'}\n[ ] incorrect\n[x] correct\n"
@stringInputTemplate: "= #{gettext 'answer'}\n"
@numberInputTemplate: "= #{gettext 'answer'} +- 0.001%\n"
@selectTemplate: "[[#{gettext 'incorrect'}, (#{gettext 'correct'}), #{gettext 'incorrect'}]]\n"
@headerTemplate: "#{gettext 'Header'}\n=====\n"
@explanationTemplate: "[explanation]\n#{gettext 'Short explanation'}\n[explanation]\n"
constructor: (element) ->
@element = element
if $(".markdown-box", @element).length != 0
@markdown_editor = CodeMirror.fromTextArea($(".markdown-box", element)[0], {
lineWrapping: true
mode: null
})
@setCurrentEditor(@markdown_editor)
# Add listeners for toolbar buttons (only present for markdown editor)
@element.on('click', '.xml-tab', @onShowXMLButton)
@element.on('click', '.format-buttons button', @onToolbarButton)
@element.on('click', '.cheatsheet-toggle', @toggleCheatsheet)
# Hide the XML text area
$(@element.find('.xml-box')).hide()
else
@createXMLEditor()
###
Creates the XML Editor and sets it as the current editor. If text is passed in,
it will replace the text present in the HTML template.
text: optional argument to override the text passed in via the HTML template
###
createXMLEditor: (text) ->
@xml_editor = CodeMirror.fromTextArea($(".xml-box", @element)[0], {
mode: "xml"
lineNumbers: true
lineWrapping: true
})
if text
@xml_editor.setValue(text)
@setCurrentEditor(@xml_editor)
$(@xml_editor.getWrapperElement()).toggleClass("CodeMirror-advanced");
# Need to refresh to get line numbers to display properly.
@xml_editor.refresh()
###
User has clicked to show the XML editor. Before XML editor is swapped in,
the user will need to confirm the one-way conversion.
###
onShowXMLButton: (e) =>
e.preventDefault();
if @cheatsheet && @cheatsheet.hasClass('shown')
@cheatsheet.toggleClass('shown')
@toggleCheatsheetVisibility()
if @confirmConversionToXml()
@createXMLEditor(MarkdownEditingDescriptor.markdownToXml(@markdown_editor.getValue()))
# Put cursor position to 0.
@xml_editor.setCursor(0)
# Hide markdown-specific toolbar buttons
$(@element.find('.editor-bar')).hide()
###
Have the user confirm the one-way conversion to XML.
Returns true if the user clicked OK, else false.
###
confirmConversionToXml: ->
# TODO: use something besides a JavaScript confirm dialog?
return confirm(gettext "If you use the Advanced Editor, this problem will be converted to XML and you will not be able to return to the Simple Editor Interface.\n\nProceed to the Advanced Editor and convert this problem to XML?")
###
Event listener for toolbar buttons (only possible when markdown editor is visible).
###
onToolbarButton: (e) =>
e.preventDefault();
selection = @markdown_editor.getSelection()
revisedSelection = null
switch $(e.currentTarget).attr('class')
when "multiple-choice-button" then revisedSelection = MarkdownEditingDescriptor.insertMultipleChoice(selection)
when "string-button" then revisedSelection = MarkdownEditingDescriptor.insertStringInput(selection)
when "number-button" then revisedSelection = MarkdownEditingDescriptor.insertNumberInput(selection)
when "checks-button" then revisedSelection = MarkdownEditingDescriptor.insertCheckboxChoice(selection)
when "dropdown-button" then revisedSelection = MarkdownEditingDescriptor.insertSelect(selection)
when "header-button" then revisedSelection = MarkdownEditingDescriptor.insertHeader(selection)
when "explanation-button" then revisedSelection = MarkdownEditingDescriptor.insertExplanation(selection)
else # ignore click
if revisedSelection != null
@markdown_editor.replaceSelection(revisedSelection)
@markdown_editor.focus()
###
Event listener for toggling cheatsheet (only possible when markdown editor is visible).
###
toggleCheatsheet: (e) =>
e.preventDefault();
if !$(@markdown_editor.getWrapperElement()).find('.simple-editor-cheatsheet')[0]
@cheatsheet = $($('#simple-editor-cheatsheet').html())
$(@markdown_editor.getWrapperElement()).append(@cheatsheet)
@toggleCheatsheetVisibility()
setTimeout (=> @cheatsheet.toggleClass('shown')), 10
###
Function to toggle cheatsheet visibility.
###
toggleCheatsheetVisibility: () =>
$('.modal-content').toggleClass('cheatsheet-is-shown')
###
Stores the current editor and hides the one that is not displayed.
###
setCurrentEditor: (editor) ->
if @current_editor
$(@current_editor.getWrapperElement()).hide()
@current_editor = editor
$(@current_editor.getWrapperElement()).show()
$(@current_editor).focus();
###
Called when save is called. Listeners are unregistered because editing the block again will
result in a new instance of the descriptor. Note that this is NOT the case for cancel--
when cancel is called the instance of the descriptor is reused if edit is selected again.
###
save: ->
@element.off('click', '.xml-tab', @changeEditor)
@element.off('click', '.format-buttons button', @onToolbarButton)
@element.off('click', '.cheatsheet-toggle', @toggleCheatsheet)
if @current_editor == @markdown_editor
{
data: MarkdownEditingDescriptor.markdownToXml(@markdown_editor.getValue())
metadata: markdown: @markdown_editor.getValue()
}
else
{
data: @xml_editor.getValue()
nullout: ['markdown']
}
@insertMultipleChoice: (selectedText) ->
return MarkdownEditingDescriptor.insertGenericChoice(selectedText, '(', ')', MarkdownEditingDescriptor.multipleChoiceTemplate)
@insertCheckboxChoice: (selectedText) ->
return MarkdownEditingDescriptor.insertGenericChoice(selectedText, '[', ']', MarkdownEditingDescriptor.checkboxChoiceTemplate)
@insertGenericChoice: (selectedText, choiceStart, choiceEnd, template) ->
if selectedText.length > 0
# Replace adjacent newlines with a single newline, strip any trailing newline
cleanSelectedText = selectedText.replace(/\n+/g, '\n').replace(/\n$/,'')
lines = cleanSelectedText.split('\n')
revisedLines = ''
for line in lines
revisedLines += choiceStart
# a stand alone x before other text implies that this option is "correct"
if /^\s*x\s+(\S)/i.test(line)
# Remove the x and any initial whitespace as long as there's more text on the line
line = line.replace(/^\s*x\s+(\S)/i, '$1')
revisedLines += 'x'
else
revisedLines += ' '
revisedLines += choiceEnd + ' ' + line + '\n'
return revisedLines
else
return template
@insertStringInput: (selectedText) ->
return MarkdownEditingDescriptor.insertGenericInput(selectedText, '= ', '', MarkdownEditingDescriptor.stringInputTemplate)
@insertNumberInput: (selectedText) ->
return MarkdownEditingDescriptor.insertGenericInput(selectedText, '= ', '', MarkdownEditingDescriptor.numberInputTemplate)
@insertSelect: (selectedText) ->
return MarkdownEditingDescriptor.insertGenericInput(selectedText, '[[', ']]', MarkdownEditingDescriptor.selectTemplate)
@insertHeader: (selectedText) ->
return MarkdownEditingDescriptor.insertGenericInput(selectedText, '', '\n====\n', MarkdownEditingDescriptor.headerTemplate)
@insertExplanation: (selectedText) ->
return MarkdownEditingDescriptor.insertGenericInput(selectedText, '[explanation]\n', '\n[explanation]', MarkdownEditingDescriptor.explanationTemplate)
@insertGenericInput: (selectedText, lineStart, lineEnd, template) ->
if selectedText.length > 0
# TODO: should this insert a newline afterwards?
return lineStart + selectedText + lineEnd
else
return template
@markdownToXml: (markdown)->
# it will contain <hint>...</hint> tags
demandHintTags = [];
toXml = `function (markdown) {
var xml = markdown,
i, splits, makeParagraph;
var responseTypes = [
'optionresponse', 'multiplechoiceresponse', 'stringresponse', 'numericalresponse', 'choiceresponse'
];
// fix DOS \r\n line endings to look like \n
xml = xml.replace(/\r\n/g, '\n');
// replace headers
xml = xml.replace(/(^.*?$)(?=\n\=\=+$)/gm, '<h3 class="hd hd-2 problem-header">$1</h3>');
xml = xml.replace(/\n^\=\=+$/gm, '');
// extract question and description(optional)
// >>question||description<< converts to
// <label>question</label> <description>description</description>
xml = xml.replace(/>>([^]+?)<</gm, function(match, questionText) {
var result = questionText.split('||'),
label = '<label>' + result[0] + '</label>' + '\n';
// don't add empty <description> tag
if (result.length === 1 || !result[1]) {
return label;
}
return label + '<description>' + result[1] + '</description>\n'
})
// Pull out demand hints, || a hint ||
var demandhints = '';
xml = xml.replace(/(^\s*\|\|.*?\|\|\s*$\n?)+/gm, function(match) { // $\n
var options = match.split('\n');
for (i = 0; i < options.length; i += 1) {
var inner = /\s*\|\|(.*?)\|\|/.exec(options[i]);
if (inner) {
//safe-lint: disable=javascript-concat-html
demandhints += ' <hint>' + inner[1].trim() + '</hint>\n';
}
}
return '';
});
// replace \n+whitespace within extended hint {{ .. }}, by a space, so the whole
// hint sits on one line.
// This is the one instance of {{ ... }} matching that permits \n
xml = xml.replace(/{{(.|\n)*?}}/gm, function(match) {
return match.replace(/\r?\n( |\t)*/g, ' ');
});
// Function used in many places to extract {{ label:: a hint }}.
// Returns a little hash with various parts of the hint:
// hint: the hint or empty, nothint: the rest
// labelassign: javascript assignment of label attribute, or empty
extractHint = function(text, detectParens) {
var curly = /\s*{{(.*?)}}/.exec(text);
var hint = '';
var label = '';
var parens = false;
var labelassign = '';
if (curly) {
text = text.replace(curly[0], '');
hint = curly[1].trim();
var labelmatch = /^(.*?)::/.exec(hint);
if (labelmatch) {
hint = hint.replace(labelmatch[0], '').trim();
label = labelmatch[1].trim();
labelassign = ' label="' + label + '"';
}
}
if (detectParens) {
if (text.length >= 2 && text[0] == '(' && text[text.length-1] == ')') {
text = text.substring(1, text.length-1)
parens = true;
}
}
return {'nothint': text, 'hint': hint, 'label': label, 'parens': parens, 'labelassign': labelassign};
}
// replace selects
// [[ a, b, (c) ]]
// [[
// a
// b
// (c)
// ]]
// <optionresponse>
// <optioninput>
// <option correct="True">AAA<optionhint label="Good Job">Yes, multiple choice is the right answer.</optionhint>
// Note: part of the option-response syntax looks like multiple-choice, so it must be processed first.
xml = xml.replace(/\[\[((.|\n)+?)\]\]/g, function(match, group1) {
// decide if this is old style or new style
if (match.indexOf('\n') == -1) { // OLD style, [[ .... ]] on one line
var options = group1.split(/\,\s*/g);
var optiontag = ' <optioninput options="(';
for (i = 0; i < options.length; i += 1) {
optiontag += "'" + options[i].replace(/(?:^|,)\s*\((.*?)\)\s*(?:$|,)/g, '$1') + "'" + (i < options.length -1 ? ',' : '');
}
optiontag += ')" correct="';
var correct = /(?:^|,)\s*\((.*?)\)\s*(?:$|,)/g.exec(group1);
if (correct) {
optiontag += correct[1];
}
optiontag += '">';
return '\n<optionresponse>\n' + optiontag + '</optioninput>\n</optionresponse>\n\n';
}
// new style [[ many-lines ]]
var lines = group1.split('\n');
var optionlines = ''
for (i = 0; i < lines.length; i++) {
var line = lines[i].trim();
if (line.length > 0) {
var textHint = extractHint(line, true);
var correctstr = ' correct="' + (textHint.parens?'True':'False') + '"';
var hintstr = '';
if (textHint.hint) {
var label = textHint.label;
if (label) {
label = ' label="' + label + '"';
}
hintstr = ' <optionhint' + label + '>' + textHint.hint + '</optionhint>';
}
optionlines += ' <option' + correctstr + '>' + textHint.nothint + hintstr + '</option>\n'
}
}
return '\n<optionresponse>\n <optioninput>\n' + optionlines + ' </optioninput>\n</optionresponse>\n\n';
});
//_____________________________________________________________________
//
// multiple choice questions
//
xml = xml.replace(/(^\s*\(.{0,3}\).*?$\n*)+/gm, function(match, p) {
var choices = '';
var shuffle = false;
var options = match.split('\n');
for(var i = 0; i < options.length; i++) {
options[i] = options[i].trim(); // trim off leading/trailing whitespace
if(options[i].length > 0) {
var value = options[i].split(/^\s*\(.{0,3}\)\s*/)[1];
var inparens = /^\s*\((.{0,3})\)\s*/.exec(options[i])[1];
var correct = /x/i.test(inparens);
var fixed = '';
if(/@/.test(inparens)) {
fixed = ' fixed="true"';
} }
if(/!/.test(inparens)) { function ctor() {
shuffle = true; this.constructor = child;
} }
var hint = extractHint(value); ctor.prototype = parent.prototype;
if (hint.hint) { child.prototype = new ctor();
value = hint.nothint; child.__super__ = parent.prototype;
value = value + ' <choicehint' + hint.labelassign + '>' + hint.hint + '</choicehint>'; return child;
};
this.MarkdownEditingDescriptor = (function(_super) {
__extends(MarkdownEditingDescriptor, _super);
MarkdownEditingDescriptor.multipleChoiceTemplate = "( ) " + (gettext('incorrect')) + "\n( ) " + (gettext('incorrect')) + "\n(x) " + (gettext('correct')) + "\n";
MarkdownEditingDescriptor.checkboxChoiceTemplate = "[x] " + (gettext('correct')) + "\n[ ] incorrect\n[x] correct\n";
MarkdownEditingDescriptor.stringInputTemplate = "= " + (gettext('answer')) + "\n";
MarkdownEditingDescriptor.numberInputTemplate = "= " + (gettext('answer')) + " +- 0.001%\n";
MarkdownEditingDescriptor.selectTemplate = "[[" + (gettext('incorrect')) + ", (" + (gettext('correct')) + "), " + (gettext('incorrect')) + "]]\n";
MarkdownEditingDescriptor.headerTemplate = "" + (gettext('Header')) + "\n=====\n";
MarkdownEditingDescriptor.explanationTemplate = "[explanation]\n" + (gettext('Short explanation')) + "\n[explanation]\n";
function MarkdownEditingDescriptor(element) {
var _this = this;
this.toggleCheatsheetVisibility = function() {
return MarkdownEditingDescriptor.prototype.toggleCheatsheetVisibility.apply(_this, arguments);
};
this.toggleCheatsheet = function(e) {
return MarkdownEditingDescriptor.prototype.toggleCheatsheet.apply(_this, arguments);
};
this.onToolbarButton = function(e) {
return MarkdownEditingDescriptor.prototype.onToolbarButton.apply(_this, arguments);
};
this.onShowXMLButton = function(e) {
return MarkdownEditingDescriptor.prototype.onShowXMLButton.apply(_this, arguments);
};
this.element = element;
if ($(".markdown-box", this.element).length !== 0) {
this.markdown_editor = CodeMirror.fromTextArea($(".markdown-box", element)[0], {
lineWrapping: true,
mode: null
});
this.setCurrentEditor(this.markdown_editor);
this.element.on('click', '.xml-tab', this.onShowXMLButton);
this.element.on('click', '.format-buttons button', this.onToolbarButton);
this.element.on('click', '.cheatsheet-toggle', this.toggleCheatsheet);
$(this.element.find('.xml-box')).hide();
} else {
this.createXMLEditor();
} }
choices += ' <choice correct="' + correct + '"' + fixed + '>' + value + '</choice>\n';
}
}
var result = '<multiplechoiceresponse>\n';
if(shuffle) {
result += ' <choicegroup type="MultipleChoice" shuffle="true">\n';
} else {
result += ' <choicegroup type="MultipleChoice">\n';
} }
result += choices;
result += ' </choicegroup>\n'; /*
result += '</multiplechoiceresponse>\n\n'; Creates the XML Editor and sets it as the current editor. If text is passed in,
return result; it will replace the text present in the HTML template.
});
text: optional argument to override the text passed in via the HTML template
// group check answers */
// [.] with {{...}} lines mixed in
xml = xml.replace(/(^\s*((\[.?\])|({{.*?}})).*?$\n*)+/gm, function(match) {
var groupString = '<choiceresponse>\n', MarkdownEditingDescriptor.prototype.createXMLEditor = function(text) {
options, value, correct; this.xml_editor = CodeMirror.fromTextArea($(".xml-box", this.element)[0], {
mode: "xml",
groupString += ' <checkboxgroup>\n'; lineNumbers: true,
options = match.split('\n'); lineWrapping: true
});
endHints = ''; // save these up to emit at the end if (text) {
this.xml_editor.setValue(text);
for (i = 0; i < options.length; i += 1) { }
if(options[i].trim().length > 0) { this.setCurrentEditor(this.xml_editor);
// detect the {{ ((A*B)) ...}} case first $(this.xml_editor.getWrapperElement()).toggleClass("CodeMirror-advanced");
// emits: <compoundhint value="A*B">AB hint</compoundhint> return this.xml_editor.refresh();
};
var abhint = /^\s*{{\s*\(\((.*?)\)\)(.*?)}}/.exec(options[i]);
if (abhint) { /*
// lone case of hint text processing outside of extractHint, since syntax here is unique User has clicked to show the XML editor. Before XML editor is swapped in,
var hintbody = abhint[2]; the user will need to confirm the one-way conversion.
hintbody = hintbody.replace('&lf;', '\n').trim() */
endHints += ' <compoundhint value="' + abhint[1].trim() +'">' + hintbody + '</compoundhint>\n';
continue; // bail
} MarkdownEditingDescriptor.prototype.onShowXMLButton = function(e) {
e.preventDefault();
value = options[i].split(/^\s*\[.?\]\s*/)[1]; if (this.cheatsheet && this.cheatsheet.hasClass('shown')) {
correct = /^\s*\[x\]/i.test(options[i]); this.cheatsheet.toggleClass('shown');
hints = ''; this.toggleCheatsheetVisibility();
// {{ selected: You’re right that apple is a fruit. }, {unselected: Remember that apple is also a fruit.}} }
var hint = extractHint(value); if (this.confirmConversionToXml()) {
if (hint.hint) { this.createXMLEditor(MarkdownEditingDescriptor.markdownToXml(this.markdown_editor.getValue()));
var inner = '{' + hint.hint + '}'; // parsing is easier if we put outer { } back this.xml_editor.setCursor(0);
var select = /{\s*(s|selected):((.|\n)*?)}/i.exec(inner); // include \n since we are downstream of extractHint() return $(this.element.find('.editor-bar')).hide();
// checkbox choicehints get their own line, since there can be two of them
// <choicehint selected="true">You’re right that apple is a fruit.</choicehint>
if (select) {
hints += '\n <choicehint selected="true">' + select[2].trim() + '</choicehint>';
}
var select = /{\s*(u|unselected):((.|\n)*?)}/i.exec(inner);
if (select) {
hints += '\n <choicehint selected="false">' + select[2].trim() + '</choicehint>';
}
// Blank out the original text only if the specific "selected" syntax is found
// That way, if the user types it wrong, at least they can see it's not processed.
if (hints) {
value = hint.nothint;
}
}
groupString += ' <choice correct="' + correct + '">' + value + hints +'</choice>\n';
}
}
groupString += endHints;
groupString += ' </checkboxgroup>\n';
groupString += '</choiceresponse>\n\n';
return groupString;
});
// replace string and numerical, numericalresponse, stringresponse
// A fine example of the function-composition programming style.
xml = xml.replace(/(^s?\=\s*(.*?$)(\n*(or|not)\=\s*(.*?$))*)+/gm, function(match, p) {
// Line split here, trim off leading xxx= in each function
var answersList = p.split('\n'),
processNumericalResponse = function (value) {
// Numeric case is just a plain leading = with a single answer
value = value.replace(/^\=\s*/, '');
var params, answer, string;
var textHint = extractHint(value);
var hintLine = '';
if (textHint.hint) {
value = textHint.nothint;
hintLine = ' <correcthint' + textHint.labelassign + '>' + textHint.hint + '</correcthint>\n'
}
if (_.contains([ '[', '(' ], value[0]) && _.contains([ ']', ')' ], value[value.length-1]) ) {
// [5, 7) or (5, 7), or (1.2345 * (2+3), 7*4 ] - range tolerance case
// = (5*2)*3 should not be used as range tolerance
string = '<numericalresponse answer="' + value + '">\n';
string += ' <formulaequationinput />\n';
string += hintLine;
string += '</numericalresponse>\n\n';
return string;
}
if (isNaN(parseFloat(value))) {
return false;
}
// Tries to extract parameters from string like 'expr +- tolerance'
params = /(.*?)\+\-\s*(.*?$)/.exec(value);
if(params) {
answer = params[1].replace(/\s+/g, ''); // support inputs like 5*2 +- 10
string = '<numericalresponse answer="' + answer + '">\n';
string += ' <responseparam type="tolerance" default="' + params[2] + '" />\n';
} else {
answer = value.replace(/\s+/g, ''); // support inputs like 5*2
string = '<numericalresponse answer="' + answer + '">\n';
}
string += ' <formulaequationinput />\n';
string += hintLine;
string += '</numericalresponse>\n\n';
return string;
},
processStringResponse = function (values) {
// First string case is s?=
var firstAnswer = values.shift(), string;
firstAnswer = firstAnswer.replace(/^s?\=\s*/, '');
var textHint = extractHint(firstAnswer);
firstAnswer = textHint.nothint;
var typ = ' type="ci"';
if (firstAnswer[0] == '|') { // this is regexp case
typ = ' type="ci regexp"';
firstAnswer = firstAnswer.slice(1).trim();
}
string = '<stringresponse answer="' + firstAnswer + '"' + typ + ' >\n';
if (textHint.hint) {
string += ' <correcthint' + textHint.labelassign + '>' + textHint.hint + '</correcthint>\n';
}
// Subsequent cases are not= or or=
for (i = 0; i < values.length; i += 1) {
var textHint = extractHint(values[i]);
var notMatch = /^not\=\s*(.*)/.exec(textHint.nothint);
if (notMatch) {
string += ' <stringequalhint answer="' + notMatch[1] + '"' + textHint.labelassign + '>' + textHint.hint + '</stringequalhint>\n';
continue;
}
var orMatch = /^or\=\s*(.*)/.exec(textHint.nothint);
if (orMatch) {
// additional_answer with answer= attribute
string += ' <additional_answer answer="' + orMatch[1] + '">';
if (textHint.hint) {
string += '<correcthint' + textHint.labelassign + '>' + textHint.hint + '</correcthint>';
}
string += '</additional_answer>\n';
}
}
string += ' <textline size="20"/>\n</stringresponse>\n\n';
return string;
};
return processNumericalResponse(answersList[0]) || processStringResponse(answersList);
});
// replace explanations
xml = xml.replace(/\[explanation\]\n?([^\]]*)\[\/?explanation\]/gmi, function(match, p1) {
var selectString = '<solution>\n<div class="detailed-solution">\n' + gettext('Explanation') + '\n\n' + p1 + '\n</div>\n</solution>';
return selectString;
});
// replace code blocks
xml = xml.replace(/\[code\]\n?([^\]]*)\[\/?code\]/gmi, function(match, p1) {
var selectString = '<pre><code>' + p1 + '</code></pre>';
return selectString;
});
// split scripts and preformatted sections, and wrap paragraphs
splits = xml.split(/(\<\/?(?:script|pre|label|description).*?\>)/g);
// Wrap a string by <p> tag when line is not already wrapped by another tag
// true when line is not already wrapped by another tag false otherwise
makeParagraph = true;
for (i = 0; i < splits.length; i += 1) {
if (/\<(script|pre|label|description)/.test(splits[i])) {
makeParagraph = false;
}
if (makeParagraph) {
splits[i] = splits[i].replace(/(^(?!\s*\<|$).*$)/gm, '<p>$1</p>');
}
if (/\<\/(script|pre|label|description)/.test(splits[i])) {
makeParagraph = true;
}
}
xml = splits.join('');
// rid white space
xml = xml.replace(/\n\n\n/g, '\n');
// if we've come across demand hints, wrap in <demandhint> at the end
if (demandhints) {
demandHintTags.push(demandhints);
}
// make selector to search responsetypes in xml
var responseTypesSelector = responseTypes.join(', ');
// make temporary xml
// safe-lint: disable=javascript-concat-html
var $xml = $($.parseXML('<prob>' + xml + '</prob>'));
responseType = $xml.find(responseTypesSelector);
// convert if there is only one responsetype
if (responseType.length === 1) {
var inputtype = responseType[0].firstElementChild
// used to decide whether an element should be placed before or after an inputtype
var beforeInputtype = true;
_.each($xml.find('prob').children(), function(child, index){
// we don't want to add the responsetype again into new xml
if (responseType[0].nodeName === child.nodeName) {
beforeInputtype = false;
return;
} }
};
/*
Have the user confirm the one-way conversion to XML.
Returns true if the user clicked OK, else false.
*/
MarkdownEditingDescriptor.prototype.confirmConversionToXml = function() {
return confirm(gettext("If you use the Advanced Editor, this problem will be converted to XML and you will not be able to return to the Simple Editor Interface.\n\nProceed to the Advanced Editor and convert this problem to XML?"));
};
/*
Event listener for toolbar buttons (only possible when markdown editor is visible).
*/
MarkdownEditingDescriptor.prototype.onToolbarButton = function(e) {
var revisedSelection, selection;
e.preventDefault();
selection = this.markdown_editor.getSelection();
revisedSelection = null;
switch ($(e.currentTarget).attr('class')) {
case "multiple-choice-button":
revisedSelection = MarkdownEditingDescriptor.insertMultipleChoice(selection);
break;
case "string-button":
revisedSelection = MarkdownEditingDescriptor.insertStringInput(selection);
break;
case "number-button":
revisedSelection = MarkdownEditingDescriptor.insertNumberInput(selection);
break;
case "checks-button":
revisedSelection = MarkdownEditingDescriptor.insertCheckboxChoice(selection);
break;
case "dropdown-button":
revisedSelection = MarkdownEditingDescriptor.insertSelect(selection);
break;
case "header-button":
revisedSelection = MarkdownEditingDescriptor.insertHeader(selection);
break;
case "explanation-button":
revisedSelection = MarkdownEditingDescriptor.insertExplanation(selection);
break;
}
if (revisedSelection !== null) {
this.markdown_editor.replaceSelection(revisedSelection);
return this.markdown_editor.focus();
}
};
/*
Event listener for toggling cheatsheet (only possible when markdown editor is visible).
*/
MarkdownEditingDescriptor.prototype.toggleCheatsheet = function(e) {
var _this = this;
e.preventDefault();
if (!$(this.markdown_editor.getWrapperElement()).find('.simple-editor-cheatsheet')[0]) {
this.cheatsheet = $($('#simple-editor-cheatsheet').html());
$(this.markdown_editor.getWrapperElement()).append(this.cheatsheet);
}
this.toggleCheatsheetVisibility();
return setTimeout((function() {
return _this.cheatsheet.toggleClass('shown');
}), 10);
};
/*
Function to toggle cheatsheet visibility.
*/
MarkdownEditingDescriptor.prototype.toggleCheatsheetVisibility = function() {
return $('.modal-content').toggleClass('cheatsheet-is-shown');
};
if (beforeInputtype) { /*
// safe-lint: disable=javascript-jquery-insert-into-target Stores the current editor and hides the one that is not displayed.
responseType[0].insertBefore(child, inputtype); */
MarkdownEditingDescriptor.prototype.setCurrentEditor = function(editor) {
if (this.current_editor) {
$(this.current_editor.getWrapperElement()).hide();
}
this.current_editor = editor;
$(this.current_editor.getWrapperElement()).show();
return $(this.current_editor).focus();
};
/*
Called when save is called. Listeners are unregistered because editing the block again will
result in a new instance of the descriptor. Note that this is NOT the case for cancel--
when cancel is called the instance of the descriptor is reused if edit is selected again.
*/
MarkdownEditingDescriptor.prototype.save = function() {
this.element.off('click', '.xml-tab', this.changeEditor);
this.element.off('click', '.format-buttons button', this.onToolbarButton);
this.element.off('click', '.cheatsheet-toggle', this.toggleCheatsheet);
if (this.current_editor === this.markdown_editor) {
return {
data: MarkdownEditingDescriptor.markdownToXml(this.markdown_editor.getValue()),
metadata: {
markdown: this.markdown_editor.getValue()
}
};
} else {
return {
data: this.xml_editor.getValue(),
nullout: ['markdown']
};
}
};
MarkdownEditingDescriptor.insertMultipleChoice = function(selectedText) {
return MarkdownEditingDescriptor.insertGenericChoice(selectedText, '(', ')', MarkdownEditingDescriptor.multipleChoiceTemplate);
};
MarkdownEditingDescriptor.insertCheckboxChoice = function(selectedText) {
return MarkdownEditingDescriptor.insertGenericChoice(selectedText, '[', ']', MarkdownEditingDescriptor.checkboxChoiceTemplate);
};
MarkdownEditingDescriptor.insertGenericChoice = function(selectedText, choiceStart, choiceEnd, template) {
var cleanSelectedText, line, lines, revisedLines, _i, _len;
if (selectedText.length > 0) {
cleanSelectedText = selectedText.replace(/\n+/g, '\n').replace(/\n$/, '');
lines = cleanSelectedText.split('\n');
revisedLines = '';
for (_i = 0, _len = lines.length; _i < _len; _i++) {
line = lines[_i];
revisedLines += choiceStart;
if (/^\s*x\s+(\S)/i.test(line)) {
line = line.replace(/^\s*x\s+(\S)/i, '$1');
revisedLines += 'x';
} else {
revisedLines += ' ';
}
revisedLines += choiceEnd + ' ' + line + '\n';
}
return revisedLines;
} else { } else {
responseType[0].appendChild(child); return template;
} }
}) };
var serializer = new XMLSerializer();
MarkdownEditingDescriptor.insertStringInput = function(selectedText) {
xml = serializer.serializeToString(responseType[0]); return MarkdownEditingDescriptor.insertGenericInput(selectedText, '= ', '', MarkdownEditingDescriptor.stringInputTemplate);
};
// remove xmlns attribute added by the serializer
xml = xml.replace(/\sxmlns=['"].*?['"]/gi, ''); MarkdownEditingDescriptor.insertNumberInput = function(selectedText) {
return MarkdownEditingDescriptor.insertGenericInput(selectedText, '= ', '', MarkdownEditingDescriptor.numberInputTemplate);
// XMLSerializer messes the indentation of XML so add newline };
// at the end of each ending tag to make the xml looks better
xml = xml.replace(/(\<\/.*?\>)(\<.*?\>)/gi, '$1\n$2'); MarkdownEditingDescriptor.insertSelect = function(selectedText) {
} return MarkdownEditingDescriptor.insertGenericInput(selectedText, '[[', ']]', MarkdownEditingDescriptor.selectTemplate);
};
// remove class attribute added on <p> tag for question title
xml = xml.replace(/\sclass=\'qtitle\'/gi, ''); MarkdownEditingDescriptor.insertHeader = function(selectedText) {
return xml; return MarkdownEditingDescriptor.insertGenericInput(selectedText, '', '\n====\n', MarkdownEditingDescriptor.headerTemplate);
}` };
responseTypesXML = [] MarkdownEditingDescriptor.insertExplanation = function(selectedText) {
responseTypesMarkdown = markdown.split(/\n\s*---\s*\n/g) return MarkdownEditingDescriptor.insertGenericInput(selectedText, '[explanation]\n', '\n[explanation]', MarkdownEditingDescriptor.explanationTemplate);
_.each responseTypesMarkdown, (responseTypeMarkdown, index) -> };
if responseTypeMarkdown.trim().length > 0
responseTypesXML.push toXml(responseTypeMarkdown) MarkdownEditingDescriptor.insertGenericInput = function(selectedText, lineStart, lineEnd, template) {
if (selectedText.length > 0) {
# combine demandhints return lineStart + selectedText + lineEnd;
demandHints = '' } else {
if demandHintTags.length return template;
## safe-lint: disable=javascript-concat-html }
demandHints = '\n<demandhint>\n' + demandHintTags.join('') + '</demandhint>' };
# make all responsetypes descendants of a single problem element MarkdownEditingDescriptor.markdownToXml = function(markdown) {
## safe-lint: disable=javascript-concat-html var demandHintTags, demandHints, finalXml, responseTypesMarkdown, responseTypesXML, toXml;
# format and return xml demandHintTags = [];
finalXml = '<problem>' + responseTypesXML.join('\n\n') + demandHints + '</problem>' toXml = function(markdown) {
return PrettyPrint.xml(finalXml); var xml = markdown,
i, splits, makeParagraph;
var responseTypes = [
'optionresponse', 'multiplechoiceresponse', 'stringresponse', 'numericalresponse', 'choiceresponse'
];
// fix DOS \r\n line endings to look like \n
xml = xml.replace(/\r\n/g, '\n');
// replace headers
xml = xml.replace(/(^.*?$)(?=\n\=\=+$)/gm, '<h3 class="hd hd-2 problem-header">$1</h3>');
xml = xml.replace(/\n^\=\=+$/gm, '');
// extract question and description(optional)
// >>question||description<< converts to
// <label>question</label> <description>description</description>
xml = xml.replace(/>>([^]+?)<</gm, function(match, questionText) {
var result = questionText.split('||'),
label = '<label>' + result[0] + '</label>' + '\n';
// don't add empty <description> tag
if (result.length === 1 || !result[1]) {
return label;
}
return label + '<description>' + result[1] + '</description>\n'
})
// Pull out demand hints, || a hint ||
var demandhints = '';
xml = xml.replace(/(^\s*\|\|.*?\|\|\s*$\n?)+/gm, function(match) { // $\n
var options = match.split('\n');
for (i = 0; i < options.length; i += 1) {
var inner = /\s*\|\|(.*?)\|\|/.exec(options[i]);
if (inner) {
//safe-lint: disable=javascript-concat-html
demandhints += ' <hint>' + inner[1].trim() + '</hint>\n';
}
}
return '';
});
// replace \n+whitespace within extended hint {{ .. }}, by a space, so the whole
// hint sits on one line.
// This is the one instance of {{ ... }} matching that permits \n
xml = xml.replace(/{{(.|\n)*?}}/gm, function(match) {
return match.replace(/\r?\n( |\t)*/g, ' ');
});
// Function used in many places to extract {{ label:: a hint }}.
// Returns a little hash with various parts of the hint:
// hint: the hint or empty, nothint: the rest
// labelassign: javascript assignment of label attribute, or empty
extractHint = function(text, detectParens) {
var curly = /\s*{{(.*?)}}/.exec(text);
var hint = '';
var label = '';
var parens = false;
var labelassign = '';
if (curly) {
text = text.replace(curly[0], '');
hint = curly[1].trim();
var labelmatch = /^(.*?)::/.exec(hint);
if (labelmatch) {
hint = hint.replace(labelmatch[0], '').trim();
label = labelmatch[1].trim();
labelassign = ' label="' + label + '"';
}
}
if (detectParens) {
if (text.length >= 2 && text[0] == '(' && text[text.length - 1] == ')') {
text = text.substring(1, text.length - 1)
parens = true;
}
}
return {
'nothint': text,
'hint': hint,
'label': label,
'parens': parens,
'labelassign': labelassign
};
}
// replace selects
// [[ a, b, (c) ]]
// [[
// a
// b
// (c)
// ]]
// <optionresponse>
// <optioninput>
// <option correct="True">AAA<optionhint label="Good Job">Yes, multiple choice is the right answer.</optionhint>
// Note: part of the option-response syntax looks like multiple-choice, so it must be processed first.
xml = xml.replace(/\[\[((.|\n)+?)\]\]/g, function(match, group1) {
// decide if this is old style or new style
if (match.indexOf('\n') == -1) { // OLD style, [[ .... ]] on one line
var options = group1.split(/\,\s*/g);
var optiontag = ' <optioninput options="(';
for (i = 0; i < options.length; i += 1) {
optiontag += "'" + options[i].replace(/(?:^|,)\s*\((.*?)\)\s*(?:$|,)/g, '$1') + "'" + (i < options.length - 1 ? ',' : '');
}
optiontag += ')" correct="';
var correct = /(?:^|,)\s*\((.*?)\)\s*(?:$|,)/g.exec(group1);
if (correct) {
optiontag += correct[1];
}
optiontag += '">';
return '\n<optionresponse>\n' + optiontag + '</optioninput>\n</optionresponse>\n\n';
}
// new style [[ many-lines ]]
var lines = group1.split('\n');
var optionlines = ''
for (i = 0; i < lines.length; i++) {
var line = lines[i].trim();
if (line.length > 0) {
var textHint = extractHint(line, true);
var correctstr = ' correct="' + (textHint.parens ? 'True' : 'False') + '"';
var hintstr = '';
if (textHint.hint) {
var label = textHint.label;
if (label) {
label = ' label="' + label + '"';
}
hintstr = ' <optionhint' + label + '>' + textHint.hint + '</optionhint>';
}
optionlines += ' <option' + correctstr + '>' + textHint.nothint + hintstr + '</option>\n'
}
}
return '\n<optionresponse>\n <optioninput>\n' + optionlines + ' </optioninput>\n</optionresponse>\n\n';
});
//_____________________________________________________________________
//
// multiple choice questions
//
xml = xml.replace(/(^\s*\(.{0,3}\).*?$\n*)+/gm, function(match, p) {
var choices = '';
var shuffle = false;
var options = match.split('\n');
for (var i = 0; i < options.length; i++) {
options[i] = options[i].trim(); // trim off leading/trailing whitespace
if (options[i].length > 0) {
var value = options[i].split(/^\s*\(.{0,3}\)\s*/)[1];
var inparens = /^\s*\((.{0,3})\)\s*/.exec(options[i])[1];
var correct = /x/i.test(inparens);
var fixed = '';
if (/@/.test(inparens)) {
fixed = ' fixed="true"';
}
if (/!/.test(inparens)) {
shuffle = true;
}
var hint = extractHint(value);
if (hint.hint) {
value = hint.nothint;
value = value + ' <choicehint' + hint.labelassign + '>' + hint.hint + '</choicehint>';
}
choices += ' <choice correct="' + correct + '"' + fixed + '>' + value + '</choice>\n';
}
}
var result = '<multiplechoiceresponse>\n';
if (shuffle) {
result += ' <choicegroup type="MultipleChoice" shuffle="true">\n';
} else {
result += ' <choicegroup type="MultipleChoice">\n';
}
result += choices;
result += ' </choicegroup>\n';
result += '</multiplechoiceresponse>\n\n';
return result;
});
// group check answers
// [.] with {{...}} lines mixed in
xml = xml.replace(/(^\s*((\[.?\])|({{.*?}})).*?$\n*)+/gm, function(match) {
var groupString = '<choiceresponse>\n',
options, value, correct;
groupString += ' <checkboxgroup>\n';
options = match.split('\n');
endHints = ''; // save these up to emit at the end
for (i = 0; i < options.length; i += 1) {
if (options[i].trim().length > 0) {
// detect the {{ ((A*B)) ...}} case first
// emits: <compoundhint value="A*B">AB hint</compoundhint>
var abhint = /^\s*{{\s*\(\((.*?)\)\)(.*?)}}/.exec(options[i]);
if (abhint) {
// lone case of hint text processing outside of extractHint, since syntax here is unique
var hintbody = abhint[2];
hintbody = hintbody.replace('&lf;', '\n').trim()
endHints += ' <compoundhint value="' + abhint[1].trim() + '">' + hintbody + '</compoundhint>\n';
continue; // bail
}
value = options[i].split(/^\s*\[.?\]\s*/)[1];
correct = /^\s*\[x\]/i.test(options[i]);
hints = '';
// {{ selected: You’re right that apple is a fruit. }, {unselected: Remember that apple is also a fruit.}}
var hint = extractHint(value);
if (hint.hint) {
var inner = '{' + hint.hint + '}'; // parsing is easier if we put outer { } back
var select = /{\s*(s|selected):((.|\n)*?)}/i.exec(inner); // include \n since we are downstream of extractHint()
// checkbox choicehints get their own line, since there can be two of them
// <choicehint selected="true">You’re right that apple is a fruit.</choicehint>
if (select) {
hints += '\n <choicehint selected="true">' + select[2].trim() + '</choicehint>';
}
var select = /{\s*(u|unselected):((.|\n)*?)}/i.exec(inner);
if (select) {
hints += '\n <choicehint selected="false">' + select[2].trim() + '</choicehint>';
}
// Blank out the original text only if the specific "selected" syntax is found
// That way, if the user types it wrong, at least they can see it's not processed.
if (hints) {
value = hint.nothint;
}
}
groupString += ' <choice correct="' + correct + '">' + value + hints + '</choice>\n';
}
}
groupString += endHints;
groupString += ' </checkboxgroup>\n';
groupString += '</choiceresponse>\n\n';
return groupString;
});
// replace string and numerical, numericalresponse, stringresponse
// A fine example of the function-composition programming style.
xml = xml.replace(/(^s?\=\s*(.*?$)(\n*(or|not)\=\s*(.*?$))*)+/gm, function(match, p) {
// Line split here, trim off leading xxx= in each function
var answersList = p.split('\n'),
processNumericalResponse = function(value) {
// Numeric case is just a plain leading = with a single answer
value = value.replace(/^\=\s*/, '');
var params, answer, string;
var textHint = extractHint(value);
var hintLine = '';
if (textHint.hint) {
value = textHint.nothint;
hintLine = ' <correcthint' + textHint.labelassign + '>' + textHint.hint + '</correcthint>\n'
}
if (_.contains(['[', '('], value[0]) && _.contains([']', ')'], value[value.length - 1])) {
// [5, 7) or (5, 7), or (1.2345 * (2+3), 7*4 ] - range tolerance case
// = (5*2)*3 should not be used as range tolerance
string = '<numericalresponse answer="' + value + '">\n';
string += ' <formulaequationinput />\n';
string += hintLine;
string += '</numericalresponse>\n\n';
return string;
}
if (isNaN(parseFloat(value))) {
return false;
}
// Tries to extract parameters from string like 'expr +- tolerance'
params = /(.*?)\+\-\s*(.*?$)/.exec(value);
if (params) {
answer = params[1].replace(/\s+/g, ''); // support inputs like 5*2 +- 10
string = '<numericalresponse answer="' + answer + '">\n';
string += ' <responseparam type="tolerance" default="' + params[2] + '" />\n';
} else {
answer = value.replace(/\s+/g, ''); // support inputs like 5*2
string = '<numericalresponse answer="' + answer + '">\n';
}
string += ' <formulaequationinput />\n';
string += hintLine;
string += '</numericalresponse>\n\n';
return string;
},
processStringResponse = function(values) {
// First string case is s?=
var firstAnswer = values.shift(), string;
firstAnswer = firstAnswer.replace(/^s?\=\s*/, '');
var textHint = extractHint(firstAnswer);
firstAnswer = textHint.nothint;
var typ = ' type="ci"';
if (firstAnswer[0] == '|') { // this is regexp case
typ = ' type="ci regexp"';
firstAnswer = firstAnswer.slice(1).trim();
}
string = '<stringresponse answer="' + firstAnswer + '"' + typ + ' >\n';
if (textHint.hint) {
string += ' <correcthint' + textHint.labelassign + '>' + textHint.hint + '</correcthint>\n';
}
// Subsequent cases are not= or or=
for (i = 0; i < values.length; i += 1) {
var textHint = extractHint(values[i]);
var notMatch = /^not\=\s*(.*)/.exec(textHint.nothint);
if (notMatch) {
string += ' <stringequalhint answer="' + notMatch[1] + '"' + textHint.labelassign + '>' + textHint.hint + '</stringequalhint>\n';
continue;
}
var orMatch = /^or\=\s*(.*)/.exec(textHint.nothint);
if (orMatch) {
// additional_answer with answer= attribute
string += ' <additional_answer answer="' + orMatch[1] + '">';
if (textHint.hint) {
string += '<correcthint' + textHint.labelassign + '>' + textHint.hint + '</correcthint>';
}
string += '</additional_answer>\n';
}
}
string += ' <textline size="20"/>\n</stringresponse>\n\n';
return string;
};
return processNumericalResponse(answersList[0]) || processStringResponse(answersList);
});
// replace explanations
xml = xml.replace(/\[explanation\]\n?([^\]]*)\[\/?explanation\]/gmi, function(match, p1) {
var selectString = '<solution>\n<div class="detailed-solution">\n' + gettext('Explanation') + '\n\n' + p1 + '\n</div>\n</solution>';
return selectString;
});
// replace code blocks
xml = xml.replace(/\[code\]\n?([^\]]*)\[\/?code\]/gmi, function(match, p1) {
var selectString = '<pre><code>' + p1 + '</code></pre>';
return selectString;
});
// split scripts and preformatted sections, and wrap paragraphs
splits = xml.split(/(\<\/?(?:script|pre|label|description).*?\>)/g);
// Wrap a string by <p> tag when line is not already wrapped by another tag
// true when line is not already wrapped by another tag false otherwise
makeParagraph = true;
for (i = 0; i < splits.length; i += 1) {
if (/\<(script|pre|label|description)/.test(splits[i])) {
makeParagraph = false;
}
if (makeParagraph) {
splits[i] = splits[i].replace(/(^(?!\s*\<|$).*$)/gm, '<p>$1</p>');
}
if (/\<\/(script|pre|label|description)/.test(splits[i])) {
makeParagraph = true;
}
}
xml = splits.join('');
// rid white space
xml = xml.replace(/\n\n\n/g, '\n');
// if we've come across demand hints, wrap in <demandhint> at the end
if (demandhints) {
demandHintTags.push(demandhints);
}
// make selector to search responsetypes in xml
var responseTypesSelector = responseTypes.join(', ');
// make temporary xml
// safe-lint: disable=javascript-concat-html
var $xml = $($.parseXML('<prob>' + xml + '</prob>'));
responseType = $xml.find(responseTypesSelector);
// convert if there is only one responsetype
if (responseType.length === 1) {
var inputtype = responseType[0].firstElementChild
// used to decide whether an element should be placed before or after an inputtype
var beforeInputtype = true;
_.each($xml.find('prob').children(), function(child, index) {
// we don't want to add the responsetype again into new xml
if (responseType[0].nodeName === child.nodeName) {
beforeInputtype = false;
return;
}
if (beforeInputtype) {
// safe-lint: disable=javascript-jquery-insert-into-target
responseType[0].insertBefore(child, inputtype);
} else {
responseType[0].appendChild(child);
}
})
var serializer = new XMLSerializer();
xml = serializer.serializeToString(responseType[0]);
// remove xmlns attribute added by the serializer
xml = xml.replace(/\sxmlns=['"].*?['"]/gi, '');
// XMLSerializer messes the indentation of XML so add newline
// at the end of each ending tag to make the xml looks better
xml = xml.replace(/(\<\/.*?\>)(\<.*?\>)/gi, '$1\n$2');
}
// remove class attribute added on <p> tag for question title
xml = xml.replace(/\sclass=\'qtitle\'/gi, '');
return xml;
};
responseTypesXML = [];
responseTypesMarkdown = markdown.split(/\n\s*---\s*\n/g);
_.each(responseTypesMarkdown, function(responseTypeMarkdown, index) {
if (responseTypeMarkdown.trim().length > 0) {
return responseTypesXML.push(toXml(responseTypeMarkdown));
}
});
demandHints = '';
if (demandHintTags.length) {
demandHints = '\n<demandhint>\n' + demandHintTags.join('') + '</demandhint>';
}
finalXml = '<problem>' + responseTypesXML.join('\n\n') + demandHints + '</problem>';
return PrettyPrint.xml(finalXml);
};
return MarkdownEditingDescriptor;
})(XModule.Descriptor);
}).call(this);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment