Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion adminui/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
'xmlcompare_desc',
'qtype_stack',
['link' => (string) new moodle_url('/question/type/stack/adminui/questionxmlcompare.php')]
),
),
];

// Set up the page object.
Expand Down
3 changes: 2 additions & 1 deletion adminui/questionxmlcompare.php
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,8 @@
$general->displaytogglelink = (new moodle_url('/question/type/stack/adminui/questionxmlcompare.php', $toggleparams))->out();
$diffonlytoggleparams = $pageparams;
$diffonlytoggleparams['diffonly'] = stack_question_xml_compare::toggle_diff_only($diffonly);
$general->diffonlytogglelink = (new moodle_url('/question/type/stack/adminui/questionxmlcompare.php', $diffonlytoggleparams))->out();
$general->diffonlytogglelink =
(new moodle_url('/question/type/stack/adminui/questionxmlcompare.php', $diffonlytoggleparams))->out();
$general->uploadform = $uploadform->render();

// Template data is intentionally flat: the page template reads the view state directly.
Expand Down
8 changes: 5 additions & 3 deletions corsscripts/ascii/extractors/allregexmatch.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { extractorError, extractorResult } from './extractorhelper.js';

// Extractor: allregexmatch
// [[extractor targetinput="ans2" type="allregexmatch" regex="^f\\(x\\)\\s*=\\s*" /]]
// Searches the entire raw input for all lines matching operation.regex and returns
// a JSON object of the form {"matches":[...]} set as answerEl.value.
export default function allregexmatch(raw, blocks, operation) {
if (!operation || !operation.regex) {
return 'ERROR';
return extractorError('asciistringextractorregexrequired', operation ? operation.type : '');
}
const pattern = new RegExp(operation.regex);
const matches = [];
Expand All @@ -17,7 +19,7 @@ export default function allregexmatch(raw, blocks, operation) {
}

if (matches.length === 0) {
return 'ERROR';
return extractorError('asciistringextractorregexnotfound', operation.regex);
}
return JSON.stringify({ matches });
return extractorResult(JSON.stringify({ matches }));
}
8 changes: 5 additions & 3 deletions corsscripts/ascii/extractors/allregexremainder.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { extractorError, extractorResult } from './extractorhelper.js';

// Extractor: allregexremainder
// [[extractor targetinput="ans2" type="allregexremainder" regex="^f\\(x\\)\\s*=\\s*" /]]
// Searches the entire raw input for all lines matching operation.regex and returns
// a JSON object of the form {"matches":[...]} set as answerEl.value. The regex itself is removed
// from the matches.
export default function allregexremainder(raw, blocks, operation) {
if (!operation || !operation.regex) {
return 'ERROR';
return extractorError('asciistringextractorregexrequired', operation ? operation.type : '');
}
const pattern = new RegExp(operation.regex);
const matches = [];
Expand All @@ -18,7 +20,7 @@ export default function allregexremainder(raw, blocks, operation) {
}

if (matches.length === 0) {
return 'ERROR';
return extractorError('asciistringextractorregexnotfound', operation.regex);
}
return JSON.stringify({ matches });
return extractorResult(JSON.stringify({ matches }));
}
25 changes: 25 additions & 0 deletions corsscripts/ascii/extractors/extractorhelper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Shared helpers for ASCII extractors.

let extractorStrings = {};

export function setExtractorStrings(strings = {}) {
extractorStrings = {
...strings
};
}

export function extractorResult(result) {
return {
result: result
};
}

export function extractorError(key, detail = '') {
let message = extractorStrings[key] || key;
if (detail !== '') {
message = message + ' ' + String(detail);
}
return {
error: message
};
}
12 changes: 7 additions & 5 deletions corsscripts/ascii/extractors/lastblock.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { extractorError, extractorResult } from './extractorhelper.js';

// Extractor: lastblock
// Returns the raw content of the last code_inline, or the full content
// of the last asciimath_block, in document order.
Expand All @@ -7,22 +9,22 @@ export default function lastblock(raw, blocks) {
for (let i = blocks.length - 1; i >= 0; i--) {
const block = blocks[i];
if (block.type === 'code_inline') {
return block.raw;
return extractorResult(block.raw);
}
if (block.type === 'asciimath_block') {
return block.raw;
return extractorResult(block.raw);
}
}
return 'ERROR';
return extractorError('asciistringextractorlastblocknotfound');
}

// Fallback: send the final non-empty line when blocks are unavailable.
const lines = raw.split(/\r?\n/);
for (let i = lines.length - 1; i >= 0; i--) {
const trimmed = lines[i].trim();
if (trimmed !== '') {
return lines[i];
return extractorResult(lines[i]);
}
}
return 'ERROR';
return extractorError('asciistringextractorlastblocknotfound');
}
6 changes: 4 additions & 2 deletions corsscripts/ascii/extractors/lastcalc.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { extractorError, extractorResult } from './extractorhelper.js';

// Extractor: lastcalc
// Returns the trimmed content of the last calculation block.
export default function lastcalc(raw, blocks) {
if (blocks) {
for (let i = blocks.length - 1; i >= 0; i--) {
if (blocks[i].type === 'calculation') {
return blocks[i].rendered.trim();
return extractorResult(blocks[i].rendered.trim());
}
}
}
return 'ERROR';
return extractorError('asciistringextractorlastcalcnotfound');
}
10 changes: 6 additions & 4 deletions corsscripts/ascii/extractors/lastexpr.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { extractorError, extractorResult } from './extractorhelper.js';

// Extractor: lastexpr
// Returns the trimmed content of the last code_inline, or the last non-empty line
// of the last asciimath_block, in document order.
Expand All @@ -7,14 +9,14 @@ export default function lastexpr(raw, blocks) {
for (let i = blocks.length - 1; i >= 0; i--) {
const block = blocks[i];
if (block.type === 'code_inline') {
return block.raw.trim();
return extractorResult(block.raw.trim());
}
if (block.type === 'asciimath_block') {
const lines = block.raw.split(/\r?\n/);
for (let j = lines.length - 1; j >= 0; j--) {
const trimmed = lines[j].trim();
if (trimmed !== '') {
return trimmed;
return extractorResult(trimmed);
}
}
}
Expand All @@ -26,8 +28,8 @@ export default function lastexpr(raw, blocks) {
for (let i = lines.length - 1; i >= 0; i--) {
const trimmed = lines[i].trim();
if (trimmed !== '') {
return trimmed;
return extractorResult(trimmed);
}
}
return 'ERROR';
return extractorError('asciistringextractorlastexprnotfound');
}
8 changes: 5 additions & 3 deletions corsscripts/ascii/extractors/lastregexmatch.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { extractorError, extractorResult } from './extractorhelper.js';

// Extractor: lastregexmatch
// [[extractor targetinput="ans2" type="lastregexmatch" regex="^f\\(x\\)\\s*=\\s*" /]]
// Note the escaped backslashes. Searches for a trimmed line matching the given expression.
// Returns the whole trimmed line.
// Scans lines in reverse order.
export default function lastregexmatch(raw, blocks, operation) {
if (!operation || !operation.regex) {
return 'ERROR';
return extractorError('asciistringextractorregexrequired', operation ? operation.type : '');
}
const pattern = new RegExp(operation.regex);

Expand All @@ -14,8 +16,8 @@ export default function lastregexmatch(raw, blocks, operation) {
for (const line of lines) {
const trimmed = line.trim();
if (pattern.test(trimmed)) {
return trimmed;
return extractorResult(trimmed);
}
}
return 'ERROR';
return extractorError('asciistringextractorregexnotfound', operation.regex);
}
8 changes: 5 additions & 3 deletions corsscripts/ascii/extractors/lastregexremainder.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { extractorError, extractorResult } from './extractorhelper.js';

// Extractor: lastregexremainder
// [[extractor targetinput="ans2" type="lastregexmatch" regex="^f\\(x\\)\\s*=\\s*" /]]
// Note the escaped backslashes. Searches for a trimmed line matching the given expression.
// Returns the whole trimmed line with the regex removed.
// Scans lines in reverse order.
export default function lastregexremainder(raw, blocks, operation) {
if (!operation || !operation.regex) {
return 'ERROR';
return extractorError('asciistringextractorregexrequired', operation ? operation.type : '');
}
const pattern = new RegExp(operation.regex);

Expand All @@ -14,8 +16,8 @@ export default function lastregexremainder(raw, blocks, operation) {
for (const line of lines) {
const trimmed = line.trim();
if (pattern.test(trimmed)) {
return trimmed.replace(pattern, '');
return extractorResult(trimmed.replace(pattern, ''));
}
}
return 'ERROR';
return extractorError('asciistringextractorregexnotfound', operation.regex);
}
19 changes: 12 additions & 7 deletions corsscripts/ascii/extractors/laststringremainder.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
import { extractorError, extractorResult } from './extractorhelper.js';

// Extractor: laststringremainder
// [[extractor targetinput="ans2" type="laststringremainder" string="Answer =" /]]
// [[extractor targetinput="ans2" type="laststringremainder" search="Answer =" /]]
// Searches for a trimmed line (with or without backslashes) matching the given string.
// Returns the remainder of the line stripped of backslashes and leading/trailing spaces.
// Scans lines in reverse order.
export default function laststringremainder(raw, blocks, operation) {
if (!operation || !operation.string) {
return 'ERROR';
// Original release had 'string' rather than 'search'. This is corrected here
// but string option retained for back compat.
if (!operation || (!operation.search && !operation.string)) {
return extractorError('asciistringextractorsearchrequired', operation ? operation.type : '');
}

const lines = raw.split('\n');
lines.reverse();
const searchstring = operation.search ?? operation.string;
for (const line of lines) {
let trimmed = line.replace(/^[\s`]+|[\s`]+$/g, '');
if (trimmed.includes(operation.string)) {
trimmed = trimmed.replace(operation.string, '');
return trimmed.replace(/^[\s`]+|[\s`]+$/g, '');
if (trimmed.includes(searchstring)) {
trimmed = trimmed.replace(searchstring, '');
return extractorResult(trimmed.replace(/^[\s`]+|[\s`]+$/g, ''));
}
}
return 'ERROR';
return extractorError('asciistringextractorsearchnotfound', searchstring);
}
9 changes: 5 additions & 4 deletions corsscripts/ascii/extractors/laststringremainderwhitespace.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { extractorError, extractorResult } from './extractorhelper.js';

// Extractor: laststringremainderwhitespace
// [[extractor targetinput="ans2" type="laststringremainderwhitespace" string="f(x) =" /]]
// Remove the requirement to write a regex.
Expand All @@ -6,7 +8,7 @@
// Scans lines in reverse order.
export default function laststringremainderwhitespace(raw, blocks, operation) {
if (!operation || !operation.search) {
return 'ERROR';
return extractorError('asciistringextractorsearchrequired', operation ? operation.type : '');
}

var match = escaperegex(operation.search);
Expand All @@ -31,10 +33,10 @@ export default function laststringremainderwhitespace(raw, blocks, operation) {
const matched = trimmed.match(pattern);
if (matched) {
const retmatch = matched[1];
return retmatch.trim();
return extractorResult(retmatch.trim());
}
}
return 'ERROR';
return extractorError('asciistringextractorsearchnotfound', operation.search);
}

function escaperegex(str) {
Expand All @@ -43,4 +45,3 @@ function escaperegex(str) {
// 2. Turn each whitespace character in the search pattern to match to zero or more spaces.
return match.replace(/\s+/g, "\\s*");
}

16 changes: 8 additions & 8 deletions corsscripts/ascii/stackascii.bundle.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions corsscripts/ascii/stackascii.bundle.js.map

Large diffs are not rendered by default.

41 changes: 40 additions & 1 deletion corsscripts/ascii/stackascii.css
Original file line number Diff line number Diff line change
@@ -1,10 +1,49 @@
.stackascii-shell {
border-radius: 5px;
max-width: 100%;
overflow: hidden;
position: relative;
}
.asciimath {
background-color: ivory;
border-radius: 5px;
border: solid 1px;
padding: 5px;
box-sizing: border-box;
height: 100%;
overflow: auto;
padding: 5px;
width: 100%;
}
.plaintext {
white-space: pre-line;
}
.stackascii-content {
box-sizing: border-box;
width: 100%;
}
.stackascii-errors {
background-color: ivory;
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
border-top: solid 1px;
box-sizing: border-box;
color: #8b0000;
display: none;
font-weight: 600;
left: 1px;
padding: 0.5rem 5px 5px;
position: absolute;
right: 1px;
bottom: 1px;
width: auto;
z-index: 1;
}
.stackascii-shell.stackascii-has-errors .stackascii-errors {
display: block;
}
.stackascii-errors:empty {
display: none;
}
.stackascii-error-message {
margin: 0;
}
Loading
Loading