feat: update question parser to preserve text structure (#232)

This commit is contained in:
Kristin Aoki
2023-02-07 14:31:41 -05:00
committed by GitHub
parent 7e8f9a2f0c
commit 613b8d16ae
6 changed files with 90 additions and 47 deletions

View File

@@ -24,7 +24,7 @@ exports[`SelectTypeWrapper snapshot 1`] = `
</ModalDialog.Title>
</ModalDialog.Header>
<ModalDialog.Body
classname="pb-6"
className="pb-6"
>
<h1>
test child

View File

@@ -31,7 +31,7 @@ export const SelectTypeWrapper = ({
</div>
</ModalDialog.Title>
</ModalDialog.Header>
<ModalDialog.Body classname="pb-6">
<ModalDialog.Body className="pb-6">
{children}
</ModalDialog.Body>
<SelectTypeFooter

View File

@@ -31,15 +31,26 @@ export const nonQuestionKeys = [
export class OLXParser {
constructor(olxString) {
this.problem = {};
this.questionData = {};
const questionOptions = {
ignoreAttributes: false,
alwaysCreateTextNode: true,
preserveOrder: true,
};
const options = {
ignoreAttributes: false,
alwaysCreateTextNode: true,
// preserveOrder: true
};
// There are two versions of the parsed XLM because the question requires the order of the
// parsed data to be preserved. However, all the other widgets need the data grouped by
// the wrapping tag.
const questionParser = new XMLParser(questionOptions);
const parser = new XMLParser(options);
this.parsedOLX = parser.parse(olxString);
this.parsedQuestionOLX = questionParser.parse(olxString);
if (_.has(this.parsedOLX, 'problem')) {
this.problem = this.parsedOLX.problem;
this.questionData = this.parsedQuestionOLX[0].problem;
}
}
@@ -276,37 +287,25 @@ export class OLXParser {
parseQuestions(problemType) {
const options = {
ignoreAttributes: false,
preserveOrder: true,
};
const builder = new XMLBuilder(options);
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
const problemArray = _.get(this.questionData[0], problemType) || this.questionData;
/* TODO: How do we uniquely identify the description?
In order to parse 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 = {
description: 'em',
};
/* 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 questionString = builder.build(serializedQuestion);
return questionString;
const questionArray = [];
problemArray.forEach(tag => {
const tagName = Object.keys(tag)[0];
if (!nonQuestionKeys.includes(tagName)) {
questionArray.push(tag);
}
});
const questionString = builder.build(questionArray);
return questionString.replace(/<description>/gm, '<em>').replace(/<\/description>/gm, '</em>');
}
getHints() {

View File

@@ -4,10 +4,21 @@ import { ProblemTypeKeys } from '../../../data/constants/problem';
class ReactStateOLXParser {
constructor(problemState) {
const parserOptions = {
// const parserOptions = {
// ignoreAttributes: false,
// alwaysCreateTextNode: true,
// };
const questionParserOptions = {
ignoreAttributes: false,
alwaysCreateTextNode: true,
// preserveOrder: true
preserveOrder: true,
};
const questionBuilderOptions = {
ignoreAttributes: false,
attributeNamePrefix: '@_',
suppressBooleanAttributes: false,
format: true,
preserveOrder: true,
};
const builderOptions = {
ignoreAttributes: false,
@@ -15,8 +26,10 @@ class ReactStateOLXParser {
suppressBooleanAttributes: false,
format: true,
};
this.parser = new XMLParser(parserOptions);
this.questionParser = new XMLParser(questionParserOptions);
// this.parser = new XMLParser(parserOptions);
this.builder = new XMLBuilder(builderOptions);
this.questionBuilder = new XMLBuilder(questionBuilderOptions);
this.problemState = problemState.problem;
}
@@ -110,7 +123,7 @@ class ReactStateOLXParser {
addQuestion() {
const { question } = this.problemState;
const questionObject = this.parser.parse(question);
const questionObject = this.questionParser.parse(question);
return questionObject;
}
@@ -123,14 +136,33 @@ class ReactStateOLXParser {
const problemObject = {
problem: {
[problemType]: {
...question,
[widget]: widgetObject,
},
...demandhint,
...solution,
},
};
return this.builder.build(problemObject);
const problem = this.builder.build(problemObject);
const questionString = this.questionBuilder.build(question);
let problemTypeTag;
switch (problemType) {
case ProblemTypeKeys.MULTISELECT:
[problemTypeTag] = problem.match(/<choiceresponse>|<choiceresponse.[^>]+>/);
break;
case ProblemTypeKeys.DROPDOWN:
[problemTypeTag] = problem.match(/<optionresponse>|<optionresponse.[^>]+>/);
break;
case ProblemTypeKeys.SINGLESELECT:
[problemTypeTag] = problem.match(/<multiplechoiceresponse>|<multiplechoiceresponse.[^>]+>/);
break;
default:
break;
}
const updatedString = `${problemTypeTag}\n${questionString}`;
const problemString = problem.replace(problemTypeTag, updatedString);
return problemString;
}
buildTextInput() {
@@ -142,14 +174,20 @@ class ReactStateOLXParser {
const problemObject = {
problem: {
[ProblemTypeKeys.TEXTINPUT]: {
...question,
...answerObject,
},
...demandhint,
...solution,
},
};
return this.builder.build(problemObject);
const problem = this.builder.build(problemObject);
const questionString = this.questionBuilder.build(question);
const [problemTypeTag] = problem.match(/<stringresponse>|<stringresponse.[^>]+>/);
const updatedString = `${problemTypeTag}\n${questionString}`;
const problemString = problem.replace(problemTypeTag, updatedString);
return problemString;
}
buildTextInputAnswersFeedback() {
@@ -200,13 +238,19 @@ class ReactStateOLXParser {
const problemObject = {
problem: {
...question,
[ProblemTypeKeys.NUMERIC]: answerObject,
...demandhint,
...solution,
},
};
return this.builder.build(problemObject);
const problem = this.builder.build(problemObject);
const questionString = this.questionBuilder.build(question);
const [problemTypeTag] = problem.match(/<numericalresponse>|<numericalresponse.[^>]+>/);
const updatedString = `${questionString}\n${problemTypeTag}`;
const problemString = problem.replace(problemTypeTag, updatedString);
return problemString;
}
buildNumericalResponse() {

View File

@@ -16,48 +16,48 @@ describe('Check React Sate OLXParser problem', () => {
const problem = olxparser.getParsedOLXData();
const stateParser = new ReactStateOLXParser({ problem });
const buildOLX = stateParser.buildOLX();
expect(buildOLX).toEqual(checkboxesOLXWithFeedbackAndHintsOLX.buildOLX);
expect(buildOLX.replace(/\s/g, '')).toBe(checkboxesOLXWithFeedbackAndHintsOLX.buildOLX.replace(/\s/g, ''));
});
test('Test dropdown with feedback and hints problem type', () => {
const olxparser = new OLXParser(dropdownOLXWithFeedbackAndHintsOLX.rawOLX);
const problem = olxparser.getParsedOLXData();
const stateParser = new ReactStateOLXParser({ problem });
const buildOLX = stateParser.buildOLX();
expect(buildOLX).toEqual(dropdownOLXWithFeedbackAndHintsOLX.buildOLX);
expect(buildOLX.replace(/\s/g, '')).toEqual(dropdownOLXWithFeedbackAndHintsOLX.buildOLX.replace(/\s/g, ''));
});
test('Test string response with feedback and hints problem type', () => {
const olxparser = new OLXParser(textInputWithFeedbackAndHintsOLX.rawOLX);
const problem = olxparser.getParsedOLXData();
const stateParser = new ReactStateOLXParser({ problem });
const buildOLX = stateParser.buildOLX();
expect(buildOLX).toEqual(textInputWithFeedbackAndHintsOLX.buildOLX);
expect(buildOLX.replace(/\s/g, '')).toEqual(textInputWithFeedbackAndHintsOLX.buildOLX.replace(/\s/g, ''));
});
test('Test multiple choice with feedback and hints problem type', () => {
const olxparser = new OLXParser(multipleChoiceWithFeedbackAndHintsOLX.rawOLX);
const problem = olxparser.getParsedOLXData();
const stateParser = new ReactStateOLXParser({ problem });
const buildOLX = stateParser.buildOLX();
expect(buildOLX).toEqual(multipleChoiceWithFeedbackAndHintsOLX.buildOLX);
expect(buildOLX.replace(/\s/g, '')).toEqual(multipleChoiceWithFeedbackAndHintsOLX.buildOLX.replace(/\s/g, ''));
});
test('Test numerical response with feedback and hints problem type', () => {
const olxparser = new OLXParser(numericInputWithFeedbackAndHintsOLX.rawOLX);
const problem = olxparser.getParsedOLXData();
const stateParser = new ReactStateOLXParser({ problem });
const buildOLX = stateParser.buildOLX();
expect(buildOLX).toEqual(numericInputWithFeedbackAndHintsOLX.buildOLX);
expect(buildOLX.replace(/\s/g, '')).toEqual(numericInputWithFeedbackAndHintsOLX.buildOLX.replace(/\s/g, ''));
});
test('Test numerical response with feedback and hints problem type with exception', () => {
const olxparser = new OLXParser(numericInputWithFeedbackAndHintsOLXException.rawOLX);
const problem = olxparser.getParsedOLXData();
const stateParser = new ReactStateOLXParser({ problem });
const buildOLX = stateParser.buildOLX();
expect(buildOLX).toEqual(numericInputWithFeedbackAndHintsOLXException.buildOLX);
expect(buildOLX.replace(/\s/g, '')).toEqual(numericInputWithFeedbackAndHintsOLXException.buildOLX.replace(/\s/g, ''));
});
test('Test string response with feedback and hints, multiple answers', () => {
const olxparser = new OLXParser(textInputWithFeedbackAndHintsOLXWithMultipleAnswers.rawOLX);
const problem = olxparser.getParsedOLXData();
const stateParser = new ReactStateOLXParser({ problem });
const buildOLX = stateParser.buildOLX();
expect(buildOLX).toEqual(textInputWithFeedbackAndHintsOLXWithMultipleAnswers.buildOLX);
expect(buildOLX.replace(/\s/g, '')).toEqual(textInputWithFeedbackAndHintsOLXWithMultipleAnswers.buildOLX.replace(/\s/g, ''));
});
});

View File

@@ -26,8 +26,8 @@ export const getDataFromOlx = ({ rawOLX, rawSettings }) => {
try {
olxParser = new OLXParser(rawOLX);
parsedProblem = olxParser.getParsedOLXData();
} catch {
console.error('The Problem Could Not Be Parsed from OLX. redirecting to Advanced editor.');
} catch (error) {
console.error('The Problem Could Not Be Parsed from OLX. redirecting to Advanced editor.', error);
return { problemType: ProblemTypeKeys.ADVANCED, rawOLX, settings: parseSettings(rawSettings) };
}
if (parsedProblem?.problemType === ProblemTypeKeys.ADVANCED) {