Skip to content

Commit 59e65fb

Browse files
committed
JMEPart.getCorrectAnswer runs expandJuxtapositions
1 parent d901c23 commit 59e65fb

5 files changed

Lines changed: 133 additions & 41 deletions

File tree

‎runtime/scripts/jme-display.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ jme.display = /** @lends Numbas.jme.display */ {
174174
}
175175
if(tree.args) {
176176
var args = tree.args.map(replace_subvars);
177-
return {tok: tree.tok, args: args};
177+
return {tok: tree.tok, args: args, bracketed: tree.bracketed};
178178
}
179179
return tree;
180180
}

‎runtime/scripts/parts/jme.js‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,16 @@ JMEPart.prototype = /** @lends Numbas.JMEPart.prototype */
285285
var settings = this.settings;
286286
var answerSimplification = Numbas.jme.collectRuleset(settings.answerSimplificationString,scope.allRulesets());
287287
var tree = jme.display.subvars(settings.correctAnswerString, scope);
288+
tree = scope.expandJuxtapositions(
289+
tree,
290+
{
291+
singleLetterVariables: settings.singleLetterVariables,
292+
noUnknownFunctions: !settings.allowUnknownFunctions,
293+
implicitFunctionComposition: settings.implicitFunctionComposition,
294+
normaliseSubscripts: true
295+
}
296+
);
297+
288298
if(!tree && this.marks>0) {
289299
this.error('part.jme.answer missing');
290300
}

‎tests/jme-runtime.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17614,7 +17614,7 @@ jme.display = /** @lends Numbas.jme.display */ {
1761417614
}
1761517615
if(tree.args) {
1761617616
var args = tree.args.map(replace_subvars);
17617-
return {tok: tree.tok, args: args};
17617+
return {tok: tree.tok, args: args, bracketed: tree.bracketed};
1761817618
}
1761917619
return tree;
1762017620
}

‎tests/numbas-runtime.js‎

Lines changed: 116 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -16950,7 +16950,7 @@ jme.display = /** @lends Numbas.jme.display */ {
1695016950
}
1695116951
if(tree.args) {
1695216952
var args = tree.args.map(replace_subvars);
16953-
return {tok: tree.tok, args: args};
16953+
return {tok: tree.tok, args: args, bracketed: tree.bracketed};
1695416954
}
1695516955
return tree;
1695616956
}
@@ -21514,6 +21514,7 @@ if(res) { \
2151421514
this.stagedAnswer = answer;
2151521515
this.setDirty(true);
2151621516
this.removeWarnings();
21517+
this.display && this.display.showWarnings();
2151721518

2151821519
if(!dontStore) {
2151921520
if(!this.question || !this.question.exam || !this.question.exam.loading) {
@@ -23224,9 +23225,7 @@ Question.prototype = /** @lends Numbas.Question.prototype */
2322423225
tryGetAttribute(q.variablesTest,q.xml,'variables',['condition','maxRuns'],[]);
2322523226
q.signals.trigger('variableDefinitionsLoaded');
2322623227
q.signals.on('variablesGenerated',function() {
23227-
var doc = Sarissa.getDomDocument();
23228-
doc.appendChild(q.originalXML.cloneNode(true)); //get a fresh copy of the original XML, to sub variables into
23229-
q.xml = doc.selectSingleNode('question');
23228+
q.xml = q.originalXML.cloneNode(true); //get a fresh copy of the original XML, to sub variables into
2323023229
q.xml.setAttribute('number',q.number);
2323123230
});
2323223231
q.signals.on(['variablesGenerated', 'rulesetsMade'], function() {
@@ -26282,6 +26281,9 @@ Numbas.queueScript('diagnostic',['util','jme','localisation','jme-variables'], f
2628226281
topic[x[0]] = x[1];
2628326282
});
2628426283
var group = dc.exam.question_groups.find(function(g) { return g.settings.name==topic_name; })
26284+
if(!group) {
26285+
return;
26286+
}
2628526287
topic.questions = [];
2628626288
for(var i=0;i<group.numQuestions;i++) {
2628726289
topic.questions.push({
@@ -26826,6 +26828,17 @@ Numbas.queueScript('marking',['util', 'jme','localisation','jme-variables','math
2682626828
} else {
2682726829
var part_result = part.mark_answer(answer, part.getScope());
2682826830
}
26831+
if(part_result.waiting_for_pre_submit) {
26832+
return jme.wrapValue({
26833+
marks: part.availableMarks(),
26834+
credit: 0,
26835+
feedback: [],
26836+
valid: false,
26837+
states: {},
26838+
state_valid: {},
26839+
values: {}
26840+
});
26841+
}
2682926842
var result = marking.finalise_state(part_result.states.mark);
2683026843
return jme.wrapValue({
2683126844
marks: part.availableMarks(),
@@ -27323,7 +27336,7 @@ Copyright 2011-14 Newcastle University
2732327336
*/
2732427337
/** @file Start the exam */
2732527338
// 'base' gives the third-party libraries on which Numbas depends
27326-
Numbas.queueScript('base',['jquery','localisation','seedrandom','knockout','sarissa'],function() {
27339+
Numbas.queueScript('base',['jquery','localisation','seedrandom','knockout'],function() {
2732727340
});
2732827341
Numbas.queueScript('start-exam',['base','util', 'exam','settings'],function() {
2732927342
for(var name in Numbas.custom_part_types) {
@@ -28979,7 +28992,7 @@ storage.init();
2897928992
});
2898028993

2898128994
/*
28982-
Copyright 2011-14 Newcastle University
28995+
Copyright 2011-25 Newcastle University
2898328996
Licensed under the Apache License, Version 2.0 (the "License");
2898428997
you may not use this file except in compliance with the License.
2898528998
You may obtain a copy of the License at
@@ -29014,6 +29027,68 @@ Numbas.queueScript('xml',['base','jme'],function() {
2901429027
* @type {Object<XMLDocument>}
2901529028
*/
2901629029

29030+
29031+
/**
29032+
* Extends the XMLDocument to emulate IE's selectNodes.
29033+
*
29034+
* @param {string} xpath_selector - The XPath expression to use.
29035+
* @param {Node} contextNode - The top node to match against.
29036+
* @returns {Array.<Node>} - The nodes matching the XPath expression.
29037+
*/
29038+
XMLDocument.prototype.selectNodes = function(xpath_selector, contextNode){
29039+
var oResult = this.evaluate(
29040+
xpath_selector,
29041+
contextNode || this,
29042+
this.documentElement,
29043+
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
29044+
null
29045+
);
29046+
var nodeList = new Array(oResult.snapshotLength);
29047+
for(var i=0; i<nodeList.length; i++){
29048+
nodeList[i] = oResult.snapshotItem(i);
29049+
}
29050+
return nodeList;
29051+
}
29052+
29053+
/**
29054+
* Extends the XMLDocument to emulate IE's `selectSingleNode`.
29055+
*
29056+
* @param {string} xpath_selector - The XPath expression to use.
29057+
* @param {Node} contextNode - this is for internal use only by the same method when called on Elements.
29058+
* @returns {Node} - The first node matching the XPath expression.
29059+
*/
29060+
XMLDocument.prototype.selectSingleNode = function(xpath_selector, contextNode) {
29061+
return this.evaluate(
29062+
xpath_selector,
29063+
contextNode || this,
29064+
this.documentElement,
29065+
XPathResult.FIRST_ORDERED_NODE_TYPE,
29066+
null
29067+
).singleNodeValue;
29068+
};
29069+
29070+
29071+
/**
29072+
* Extends the Element to emulate IE's selectNodes.
29073+
* @param {string} xpath_selector - The XPath expression to use.
29074+
* @returns {Array.<Node>} - The result of the XPath search.
29075+
*/
29076+
Element.prototype.selectNodes = function(xpath_selector){
29077+
return this.ownerDocument.selectNodes(xpath_selector, this);
29078+
};
29079+
29080+
29081+
/**
29082+
* Extends the Element to emulate IE's `selectSingleNode`.
29083+
*
29084+
* @param {string} xpath_selector - The XPath expression to use.
29085+
* @returns {Node} - The first node matching the XPath expression.
29086+
*/
29087+
Element.prototype.selectSingleNode = function(xpath_selector){
29088+
return this.ownerDocument.selectSingleNode(xpath_selector, this);
29089+
};
29090+
29091+
2901729092
/** @namespace Numbas.xml */
2901829093
var xml = Numbas.xml = {
2901929094
/** DOM parser to use to parse XML.
@@ -29040,14 +29115,13 @@ var xml = Numbas.xml = {
2904029115
loadXML: function(xmlstring)
2904129116
{
2904229117
//parse the XML document
29043-
var doc = xml.dp.parseFromString(xmlstring,'text/xml');
29118+
const parser = new DOMParser();
29119+
var doc = parser.parseFromString(xmlstring,'text/xml');
2904429120
//check for errors
29045-
if(Sarissa.getParseErrorText(doc) != Sarissa.PARSED_OK)
29046-
{
29047-
throw(new Numbas.Error('xml.could not load',{message: Numbas.util.escapeHTML(Sarissa.getParseErrorText(doc))}));
29121+
const errorNode = doc.querySelector("parsererror");
29122+
if (errorNode) {
29123+
throw(new Numbas.Error('xml.could not load',{message: Numbas.util.escapeHTML(errorNode.textContent)}));
2904829124
}
29049-
//allow XPath to be used to select nodes
29050-
doc.setProperty('SelectionLanguage','XPath');
2905129125
//convert all the attribute names to lower case
2905229126
var es = doc.selectNodes('descendant::*');
2905329127
for(var i=0; i<es.length; i++)
@@ -29255,33 +29329,26 @@ var xml = Numbas.xml = {
2925529329
$(this).replaceWith(localString);
2925629330
});
2925729331
return template;
29258-
},
29259-
/** Transform an XML node using the given XSL template, returning a string representation of the transformed XML.
29260-
*
29261-
* @param {Element} template
29262-
* @param {Element} xml
29263-
* @returns {string}
29264-
*/
29265-
transform: function(template,xml) {
29266-
/** Is the browser Internet Explorer?
29267-
*
29268-
* @returns {boolean}
29269-
*/
29270-
function isIE() {
29271-
var ua = window.navigator.userAgent; //Check the userAgent property of the window.navigator object
29272-
var msie = ua.indexOf('MSIE '); // IE 10 or older
29273-
var trident = ua.indexOf('Trident/'); //IE 11
29274-
29275-
return (msie > 0 || trident > 0);
29276-
}
29277-
var r;
29278-
if(!isIE()) {
29279-
r = $.xsl.transform(template,xml);
29280-
} else {
29281-
var s = xml.transformNode(template);
29282-
r = {string: s, error: ''};
29283-
}
29284-
return r.string;
29332+
},
29333+
/** Transform an XML node using the given XSL template, returning a string representation of the transformed XML.
29334+
*
29335+
* @param {Element} template
29336+
* @param {Element} xml
29337+
* @returns {string}
29338+
*/
29339+
transform: function(template,xml) {
29340+
const container = xml.ownerDocument.createElement('container');
29341+
xml = xml.cloneNode(true);
29342+
container.append(xml);
29343+
29344+
const processor = new XSLTProcessor();
29345+
processor.importStylesheet(template);
29346+
const doc = processor.transformToDocument(container);
29347+
const serializer = new XMLSerializer();
29348+
29349+
const string = serializer.serializeToString(doc);
29350+
29351+
return string;
2928529352
},
2928629353
/** Is the given node empty? True if it has no children.
2928729354
*
@@ -32728,6 +32795,16 @@ JMEPart.prototype = /** @lends Numbas.JMEPart.prototype */
3272832795
var settings = this.settings;
3272932796
var answerSimplification = Numbas.jme.collectRuleset(settings.answerSimplificationString,scope.allRulesets());
3273032797
var tree = jme.display.subvars(settings.correctAnswerString, scope);
32798+
tree = scope.expandJuxtapositions(
32799+
tree,
32800+
{
32801+
singleLetterVariables: settings.singleLetterVariables,
32802+
noUnknownFunctions: !settings.allowUnknownFunctions,
32803+
implicitFunctionComposition: settings.implicitFunctionComposition,
32804+
normaliseSubscripts: true
32805+
}
32806+
);
32807+
3273132808
if(!tree && this.marks>0) {
3273232809
this.error('part.jme.answer missing');
3273332810
}

‎tests/parts/part-tests.mjs‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,11 @@ Numbas.queueScript('part_tests',['qunit','json','jme','localisation','parts/numb
532532
assert.equal(res.credit,1,'"1620000" correct');
533533
});
534534

535+
QUnit.test('Expand juxtapositions in the correct answer', async function(assert) {
536+
var p = createPartFromJSON({type:'jme', answer: '(alpha(x))^2', allowUnknownFunctions: false});
537+
assert.equal(p.getCorrectAnswer(p.getScope()), '(alpha*x)^2');
538+
});
539+
535540
QUnit.module('Pattern match');
536541
QUnit.test('Answer is "hi+"', async function(assert) {
537542
var p = createPartFromJSON({type:'patternmatch', answer: 'hi+', displayAnswer: 'hi'});

0 commit comments

Comments
 (0)