Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: fix questions formatting issues #208

Closed
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
93 changes: 62 additions & 31 deletions src/editors/containers/ProblemEditor/data/OLXParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@ export class OLXParser {
alwaysCreateTextNode: true,
// preserveOrder: true
};
this.olxString = olxString;
const parser = new XMLParser(options);
this.parsedOLX = parser.parse(olxString);
if (_.has(this.parsedOLX, 'problem')) {
this.problem = this.parsedOLX.problem;
const parsedOLX = parser.parse(olxString);
if (_.has(parsedOLX, 'problem')) {
this.problem = parsedOLX.problem;
}
}

Expand Down Expand Up @@ -274,37 +275,67 @@ export class OLXParser {
}

parseQuestions(problemType) {
const builder = new XMLBuilder();
const problemObject = _.get(this.problem, problemType);
let questionObject = {};
/* TODO: How do we uniquely identify the label and description?
In order to parse label and description, there should be two states
and settings should be introduced to edit the label and description.
In turn editing the settings update the state and then it can be added to
the parsed OLX.
*/
const tagMap = {
label: 'strong',
description: 'em',
const getQuestionObject = (parsedOlx) => {
const hasNonQuestionKeys = (obj) => !(_.some(nonQuestionKeys, (key) => _.has(obj, key)));
const isWhiteSpace = (obj) => _.get(obj, '#text', 'nonemptystring').trim() !== '';
const transformIntoQuestionObject = (root) => _.filter(_.filter(root, isWhiteSpace), hasNonQuestionKeys);
let questionObject = {};

// Try to find question within problemType node; odd path because of `preserveOrder` option used for parsing.
const problemTypeObject = _.get(
_.find(parsedOlx[0].problem, (obj) => _.has(obj, problemType)),
problemType,
{},
);
questionObject = transformIntoQuestionObject(problemTypeObject);

// If haven't found any question nodes, check if question is stored in the problem node.
if (_.isEmpty(questionObject)) {
questionObject = transformIntoQuestionObject(parsedOlx[0].problem);
}
return questionObject;
};

/* Only numerical response has different ways to generate OLX, test with
numericInputWithFeedbackAndHintsOLXException and numericInputWithFeedbackAndHintsOLX
shows the different ways the olx can be generated.
*/
if (_.isArray(problemObject)) {
questionObject = _.omitBy(problemObject[0], (value, key) => _.includes(nonQuestionKeys, key));
} else {
questionObject = _.omitBy(problemObject, (value, key) => _.includes(nonQuestionKeys, key));
}
// Check if problem tag itself will have question and descriptions.
if (_.isEmpty(questionObject)) {
questionObject = _.omitBy(this.problem, (value, key) => _.includes(nonQuestionKeys, key));
}
const serializedQuestion = _.mapKeys(questionObject, (value, key) => _.get(tagMap, key, key));
const mapQuestionTags = (questionObject) => {
/* TODO: How do we uniquely identify the label and description?
In order to parse label and description, there should be two states
and settings should be introduced to edit the label and description.
In turn editing the settings update the state and then it can be added to
the parsed OLX.
*/
const tagMap = {
label: 'strong',
description: 'em',
};

const questionString = builder.build(serializedQuestion);
return questionString;
return _.map(questionObject, (obj) => _.mapKeys(obj, (value, key) => _.get(tagMap, key, key)));
};

// To be able to generate question's HTML, such that it respects the initial
// order of elements and styles stored in the OLX, we parse and build
// original `olxString` with `preserveOrder: true`. We parse and build the
// question separately, because the JSON structure produced with
// `preserveOrder: true` option is more complex and hard to work with, and
// since the order of nested elements is not important in other places, we
// restrict the complexity of the produced JSON within the scope of this
// method.
const parser = new XMLParser({
preserveOrder: true,
trimValues: false,
ignoreAttributes: false,
});
const parsedOlx = parser.parse(this.olxString);

const questionObject = getQuestionObject(parsedOlx);
const serializedQuestion = mapQuestionTags(questionObject);

const builder = new XMLBuilder({
preserveOrder: true,
attributeNamePrefix: '@_',
ignoreAttributes: false,
format: false,
});
return builder.build(serializedQuestion);
Cup0fCoffee marked this conversation as resolved.
Show resolved Hide resolved
}

getHints() {
Expand Down
48 changes: 29 additions & 19 deletions src/editors/containers/ProblemEditor/data/ReactStateOLXParser.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
import _ from 'lodash-es';
import { XMLParser, XMLBuilder } from 'fast-xml-parser';
import { XMLBuilder } from 'fast-xml-parser';
import { ProblemTypeKeys } from '../../../data/constants/problem';

class ReactStateOLXParser {
constructor(problemState) {
const parserOptions = {
this.parserOptions = {
ignoreAttributes: false,
alwaysCreateTextNode: true,
// preserveOrder: true
};
const builderOptions = {
this.builderOptions = {
ignoreAttributes: false,
attributeNamePrefix: '@_',
suppressBooleanAttributes: false,
format: true,
};
this.parser = new XMLParser(parserOptions);
this.builder = new XMLBuilder(builderOptions);
this.builder = new XMLBuilder(this.builderOptions);
this.problemState = problemState.problem;
}

Expand Down Expand Up @@ -96,42 +94,53 @@ class ReactStateOLXParser {
return compoundhint;
}

addQuestion() {
buildOLXWithQuestion(problemObject, problemType) {
const olxWithoutQuestion = this.builder.build(problemObject);

return this.insertQuestionIntoOLX(olxWithoutQuestion, problemType);
}

insertQuestionIntoOLX(olx, tagName) {
const { question } = this.problemState;
const questionObject = this.parser.parse(question);
return questionObject;

const tagPattern = new RegExp(`<${tagName}[^>]*>`);
const match = olx.match(tagPattern);
const insertIndex = match.index + match[0].length;

const olxWithQuestion = `${olx.slice(0, insertIndex)}\n${question}${olx.slice(insertIndex)}`;

return olxWithQuestion;
}

buildMultiSelectProblem(problemType, widget, option) {
const question = this.addQuestion();
const widgetObject = this.addMultiSelectAnswers(option);
const demandhint = this.addHints();
const problemObject = {
problem: {
[problemType]: {
...question,
[widget]: widgetObject,
},
...demandhint,
},
};
return this.builder.build(problemObject);

return this.buildOLXWithQuestion(problemObject, problemType);
}

buildTextInput() {
const question = this.addQuestion();
const problemType = ProblemTypeKeys.TEXTINPUT;
const demandhint = this.addHints();
const answerObject = this.buildTextInputAnswersFeedback();
const problemObject = {
problem: {
[ProblemTypeKeys.TEXTINPUT]: {
...question,
[problemType]: {
...answerObject,
},
...demandhint,
},
};
return this.builder.build(problemObject);

return this.buildOLXWithQuestion(problemObject, problemType);
}

buildTextInputAnswersFeedback() {
Expand Down Expand Up @@ -175,17 +184,18 @@ class ReactStateOLXParser {
}

buildNumericInput() {
const question = this.addQuestion();
const demandhint = this.addHints();
const answerObject = this.buildNumericalResponse();
const problemObject = {
problem: {
...question,
[ProblemTypeKeys.NUMERIC]: answerObject,
...demandhint,
},
};
return this.builder.build(problemObject);

// Passing 'problem' as problem type, because for this type of problems the question has to be inserted under
// <problem> tag, instead of <${problemType}> tag.
return this.buildOLXWithQuestion(problemObject, 'problem');
}

buildNumericalResponse() {
Expand Down
Loading