/** * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> * <p> * Licensed under the Apache License, Version 2.0 (the "License"); <br> * you may not use this file except in compliance with the License.<br> * You may obtain a copy of the License at the * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> * <p> * Unless required by applicable law or agreed to in writing,<br> * software distributed under the License is distributed on an "AS IS" BASIS, <br> * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> * See the License for the specific language governing permissions and <br> * limitations under the License. * <p> * Initial code contributed and copyrighted by<br> * frentix GmbH, http://www.frentix.com * <p> */ package org.olat.ims.qti21.model.xml; import static org.olat.ims.qti21.QTI21Constants.MAXSCORE_IDENTIFIER; import static org.olat.ims.qti21.QTI21Constants.MINSCORE_IDENTIFIER; import java.util.ArrayList; import java.util.List; import org.olat.ims.qti21.QTI21Constants; import org.olat.ims.qti21.model.QTI21QuestionType; import uk.ac.ed.ph.jqtiplus.node.content.xhtml.text.P; import uk.ac.ed.ph.jqtiplus.node.expression.general.BaseValue; import uk.ac.ed.ph.jqtiplus.node.expression.general.Variable; import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem; import uk.ac.ed.ph.jqtiplus.node.item.ModalFeedback; import uk.ac.ed.ph.jqtiplus.node.item.interaction.EndAttemptInteraction; import uk.ac.ed.ph.jqtiplus.node.item.interaction.Interaction; import uk.ac.ed.ph.jqtiplus.node.item.response.declaration.ResponseDeclaration; import uk.ac.ed.ph.jqtiplus.node.item.response.processing.ResponseCondition; import uk.ac.ed.ph.jqtiplus.node.item.response.processing.ResponseIf; import uk.ac.ed.ph.jqtiplus.node.item.response.processing.ResponseProcessing; import uk.ac.ed.ph.jqtiplus.node.item.response.processing.ResponseRule; import uk.ac.ed.ph.jqtiplus.node.item.response.processing.SetOutcomeValue; import uk.ac.ed.ph.jqtiplus.node.outcome.declaration.OutcomeDeclaration; import uk.ac.ed.ph.jqtiplus.node.shared.declaration.DefaultValue; import uk.ac.ed.ph.jqtiplus.serialization.QtiSerializer; import uk.ac.ed.ph.jqtiplus.types.Identifier; import uk.ac.ed.ph.jqtiplus.value.BaseType; import uk.ac.ed.ph.jqtiplus.value.FloatValue; import uk.ac.ed.ph.jqtiplus.value.IdentifierValue; import uk.ac.ed.ph.jqtiplus.value.Value; /** * * * Initial date: 08.12.2015<br> * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ public abstract class AssessmentItemBuilder { protected final AssessmentItem assessmentItem; protected final QtiSerializer qtiSerializer; protected final AssessmentHtmlBuilder htmlHelper; protected final BadRessourceHelper builderHelper; private ScoreBuilder minScoreBuilder; private ScoreBuilder maxScoreBuilder; protected ModalFeedbackBuilder hint; protected ModalFeedbackBuilder emptyFeedback, answeredFeedback; protected ModalFeedbackBuilder correctFeedback, incorrectFeedback; protected ModalFeedbackBuilder correctSolutionFeedback; private List<ModalFeedbackBuilder> additionalFeedbacks = new ArrayList<>(); public AssessmentItemBuilder(AssessmentItem assessmentItem, QtiSerializer qtiSerializer) { this.assessmentItem = assessmentItem; this.qtiSerializer = qtiSerializer; builderHelper = new BadRessourceHelper(); htmlHelper = new AssessmentHtmlBuilder(qtiSerializer); extract(); } public AssessmentItem getAssessmentItem() { return assessmentItem; } public abstract QTI21QuestionType getQuestionType(); protected void extract() { extractMinScore(); extractMaxScore(); extractModalFeedbacks(); } private void extractMinScore() { OutcomeDeclaration outcomeDeclaration = assessmentItem.getOutcomeDeclaration(MINSCORE_IDENTIFIER); if(outcomeDeclaration != null) { DefaultValue defaultValue = outcomeDeclaration.getDefaultValue(); if(defaultValue != null) { Value minScoreValue = defaultValue.evaluate(); if(minScoreValue instanceof FloatValue) { Double minScore = new Double(((FloatValue)minScoreValue).doubleValue()); minScoreBuilder = new ScoreBuilder(minScore, outcomeDeclaration); } } } } private void extractMaxScore() { OutcomeDeclaration outcomeDeclaration = assessmentItem.getOutcomeDeclaration(MAXSCORE_IDENTIFIER); if(outcomeDeclaration != null) { DefaultValue defaultValue = outcomeDeclaration.getDefaultValue(); if(defaultValue != null) { Value maxScoreValue = defaultValue.evaluate(); if(maxScoreValue instanceof FloatValue) { Double maxScore = new Double(((FloatValue)maxScoreValue).doubleValue()); maxScoreBuilder = new ScoreBuilder(maxScore, outcomeDeclaration); } } } } private void extractModalFeedbacks() { List<ModalFeedback> feedbacks = assessmentItem.getModalFeedbacks(); for(ModalFeedback feedback:feedbacks) { ModalFeedbackBuilder feedbackBuilder = new ModalFeedbackBuilder(assessmentItem, feedback); if(feedbackBuilder.isCorrectRule()) { correctFeedback = feedbackBuilder; } else if(feedbackBuilder.isIncorrectRule()) { incorrectFeedback = feedbackBuilder; } else if(feedbackBuilder.isCorrectSolutionRule() || (feedback.getOutcomeIdentifier() != null && QTI21Constants.CORRECT_SOLUTION_IDENTIFIER.equals(feedback.getOutcomeIdentifier()))) { correctSolutionFeedback = feedbackBuilder; } else if(feedbackBuilder.isEmptyRule()) { emptyFeedback = feedbackBuilder; } else if(feedbackBuilder.isAnsweredRule()) { answeredFeedback = feedbackBuilder; } else if(feedbackBuilder.isHint()) { hint = feedbackBuilder; } else { additionalFeedbacks.add(feedbackBuilder); } } } public String getTitle() { return assessmentItem.getTitle(); } public void setTitle(String title) { assessmentItem.setTitle(title); } public abstract String getQuestion(); public abstract void setQuestion(String question); public ScoreBuilder getMinScoreBuilder() { return minScoreBuilder; } public void setMinScore(Double minScore) { if(minScoreBuilder == null) { minScoreBuilder = new ScoreBuilder(minScore, null); } else { minScoreBuilder.setScore(minScore); } } public ScoreBuilder getMaxScoreBuilder() { return maxScoreBuilder; } public void setMaxScore(Double maxScore) { if(maxScoreBuilder == null) { maxScoreBuilder = new ScoreBuilder(maxScore, null); } else { maxScoreBuilder.setScore(maxScore); } } public ModalFeedbackBuilder getHint() { return hint; } public ModalFeedbackBuilder createHint() { hint = new ModalFeedbackBuilder(assessmentItem); return hint; } public void removeHint() { hint = null; } public ModalFeedbackBuilder getCorrectFeedback() { return correctFeedback; } public ModalFeedbackBuilder createCorrectFeedback() { correctFeedback = new ModalFeedbackBuilder(assessmentItem); return correctFeedback; } public void removeCorrectFeedback() { correctFeedback = null; } public ModalFeedbackBuilder getEmptyFeedback() { return emptyFeedback; } public ModalFeedbackBuilder createEmptyFeedback() { emptyFeedback = new ModalFeedbackBuilder(assessmentItem); return emptyFeedback; } public void removeEmptyFeedback() { emptyFeedback = null; } public ModalFeedbackBuilder getAnsweredFeedback() { return answeredFeedback; } public ModalFeedbackBuilder createAnsweredFeedback() { answeredFeedback = new ModalFeedbackBuilder(assessmentItem); return answeredFeedback; } public void removeAnsweredFeedback() { answeredFeedback = null; } public ModalFeedbackBuilder getIncorrectFeedback() { return incorrectFeedback; } public ModalFeedbackBuilder createIncorrectFeedback() { incorrectFeedback = new ModalFeedbackBuilder(assessmentItem); return incorrectFeedback; } public void removeIncorrectFeedback() { incorrectFeedback = null; } public ModalFeedbackBuilder createCorrectSolutionFeedback() { correctSolutionFeedback = new ModalFeedbackBuilder(assessmentItem); return correctSolutionFeedback; } public ModalFeedbackBuilder getCorrectSolutionFeedback() { return correctSolutionFeedback; } public void removeCorrectSolutionFeedback() { correctSolutionFeedback = null; } public BadRessourceHelper getHelper() { return builderHelper; } public AssessmentHtmlBuilder getHtmlHelper() { return htmlHelper; } public List<String> getInteractionNames() { List<Interaction> interactions = assessmentItem.getItemBody().findInteractions(); List<String> interactionNames = new ArrayList<>(interactions.size()); for(Interaction interaction:interactions) { String interactionName = interaction.getQtiClassName(); interactionNames.add(interactionName); } return interactionNames; } public final void build() { List<OutcomeDeclaration> outcomeDeclarations = assessmentItem.getOutcomeDeclarations(); outcomeDeclarations.clear(); ResponseProcessing responseProcessing = assessmentItem.getResponseProcessing(); List<ResponseRule> responseRules = responseProcessing.getResponseRules(); responseRules.clear(); List<ResponseDeclaration> responseDeclarations = assessmentItem.getResponseDeclarations(); responseDeclarations.clear(); List<ModalFeedback> modalFeedbacks = assessmentItem.getModalFeedbacks(); modalFeedbacks.clear(); buildItemBody(); buildResponseAndOutcomeDeclarations(); buildModalFeedbacksAndHints(outcomeDeclarations, responseRules); buildMinMaxScores(outcomeDeclarations, responseRules); buildMainScoreRule(outcomeDeclarations, responseRules); buildHint(outcomeDeclarations, responseRules); } protected abstract void buildItemBody(); protected abstract void buildResponseAndOutcomeDeclarations(); protected void ensureFeedbackBasicOutcomeDeclaration() { AssessmentItemFactory.ensureFeedbackBasicOutcomeDeclaration(assessmentItem); } protected abstract void buildMainScoreRule(List<OutcomeDeclaration> outcomeDeclarations, List<ResponseRule> responseRules); /** * * @param outcomeDeclarations * @param responseRules */ protected void buildHint(List<OutcomeDeclaration> outcomeDeclarations, List<ResponseRule> responseRules) { if(hint == null) return; //response declaration -> identifier=HINTREQUEST -> for the end attempt interaction ResponseDeclaration hintResponseDeclaration = AssessmentItemFactory .createHintRequestResponseDeclaration(assessmentItem); assessmentItem.getResponseDeclarations().add(hintResponseDeclaration); //outcome declaration -> identifier=HINTFEEDBACKMODAL -> for processing and feedback OutcomeDeclaration modalOutcomeDeclaration = AssessmentItemFactory .createOutcomeDeclarationForHint(assessmentItem); outcomeDeclarations.add(modalOutcomeDeclaration); //the body P paragraph = new P(assessmentItem.getItemBody()); assessmentItem.getItemBody().getBlocks().add(paragraph); EndAttemptInteraction endAttemptInteraction = new EndAttemptInteraction(paragraph); endAttemptInteraction.setResponseIdentifier(QTI21Constants.HINT_REQUEST_IDENTIFIER); endAttemptInteraction.setTitle(hint.getTitle()); paragraph.getInlines().add(endAttemptInteraction); //the feedback ModalFeedback emptyModalFeedback = AssessmentItemFactory .createModalFeedback(assessmentItem, QTI21Constants.HINT_FEEDBACKMODAL_IDENTIFIER, QTI21Constants.HINT_IDENTIFIER, hint.getTitle(), hint.getText()); assessmentItem.getModalFeedbacks().add(emptyModalFeedback); //the response processing ResponseCondition rule = new ResponseCondition(assessmentItem.getResponseProcessing()); responseRules.add(0, rule); ResponseIf responseIf = new ResponseIf(rule); rule.setResponseIf(responseIf); /* <responseIf> <variable identifier="HINTREQUEST"/> <setOutcomeValue identifier="FEEDBACK"> <baseValue baseType="identifier">HINT</baseValue> </setOutcomeValue> </responseIf> */ Variable variable = new Variable(responseIf); variable.setIdentifier(QTI21Constants.HINT_REQUEST_CLX_IDENTIFIER); responseIf.getExpressions().add(variable); SetOutcomeValue hintVar = new SetOutcomeValue(responseIf); hintVar.setIdentifier(QTI21Constants.HINT_FEEDBACKMODAL_IDENTIFIER); BaseValue hintVal = new BaseValue(hintVar); hintVal.setBaseTypeAttrValue(BaseType.IDENTIFIER); hintVal.setSingleValue(new IdentifierValue(QTI21Constants.HINT)); hintVar.setExpression(hintVal); responseIf.getResponseRules().add(hintVar); } /** * Add feedbackbasic and feedbackmodal outcomes * @param outcomeDeclarations * @param responseRules */ protected void buildModalFeedbacksAndHints(List<OutcomeDeclaration> outcomeDeclarations, List<ResponseRule> responseRules) { if(correctFeedback != null || incorrectFeedback != null || emptyFeedback != null || answeredFeedback != null || correctSolutionFeedback != null || additionalFeedbacks.size() > 0) { ensureFeedbackBasicOutcomeDeclaration(); OutcomeDeclaration modalOutcomeDeclaration = AssessmentItemFactory .createOutcomeDeclarationForFeedbackModal(assessmentItem); outcomeDeclarations.add(modalOutcomeDeclaration); List<ModalFeedback> modalFeedbacks = assessmentItem.getModalFeedbacks(); if(correctFeedback != null) { appendModalFeedback(correctFeedback, QTI21Constants.CORRECT, modalFeedbacks, responseRules); } //correct solution must be set before the "incorrect" feedback if(correctSolutionFeedback != null) { OutcomeDeclaration correctSolutionOutcomeDeclaration = AssessmentItemFactory .createOutcomeDeclarationForCorrectSolutionFeedbackModal(assessmentItem); outcomeDeclarations.add(correctSolutionOutcomeDeclaration); } if(incorrectFeedback != null || correctSolutionFeedback != null) { appendCorrectSolutionAndIncorrectModalFeedback(modalFeedbacks, responseRules); } if(emptyFeedback != null) { appendModalFeedback(emptyFeedback, QTI21Constants.EMPTY, modalFeedbacks, responseRules); } if(answeredFeedback != null) { appendModalFeedback(answeredFeedback, QTI21Constants.ANSWERED, modalFeedbacks, responseRules); } } } protected void appendCorrectSolutionAndIncorrectModalFeedback(List<ModalFeedback> modalFeedbacks, List<ResponseRule> responseRules) { Identifier correctSolutionFeedbackIdentifier = null; if(correctSolutionFeedback != null) { correctSolutionFeedbackIdentifier = correctSolutionFeedback.getIdentifier(); ModalFeedback modalFeedback = AssessmentItemFactory.createModalFeedback(assessmentItem, QTI21Constants.CORRECT_SOLUTION_IDENTIFIER, correctSolutionFeedback.getIdentifier(), correctSolutionFeedback.getTitle(), correctSolutionFeedback.getText()); modalFeedbacks.add(modalFeedback); } Identifier incorrectFeedbackIdentifier = null; if(incorrectFeedback != null) { incorrectFeedbackIdentifier = incorrectFeedback.getIdentifier(); ModalFeedback modalFeedback = AssessmentItemFactory .createModalFeedback(assessmentItem, incorrectFeedback.getIdentifier(), incorrectFeedback.getTitle(), incorrectFeedback.getText()); modalFeedbacks.add(modalFeedback); } ResponseCondition feedbackCondition = AssessmentItemFactory .createCorrectSolutionModalFeedbackBasicRule(assessmentItem.getResponseProcessing(), correctSolutionFeedbackIdentifier, incorrectFeedbackIdentifier, hint != null); responseRules.add(feedbackCondition); } protected void appendModalFeedback(ModalFeedbackBuilder feedbackBuilder, String inCorrect, List<ModalFeedback> modalFeedbacks, List<ResponseRule> responseRules) { ModalFeedback modalFeedback = AssessmentItemFactory .createModalFeedback(assessmentItem, feedbackBuilder.getIdentifier(), feedbackBuilder.getTitle(), feedbackBuilder.getText()); modalFeedbacks.add(modalFeedback); ResponseCondition feedbackCondition = AssessmentItemFactory .createModalFeedbackBasicRule(assessmentItem.getResponseProcessing(), feedbackBuilder.getIdentifier(), inCorrect, hint != null); responseRules.add(feedbackCondition); } /** * Add outcome declaration for score, min. score and mx. score. * and the response rules which ensure that the score is between * the max. score and min. score. * * @param outcomeDeclarations * @param responseRules */ protected final void buildMinMaxScores(List<OutcomeDeclaration> outcomeDeclarations, List<ResponseRule> responseRules) { if((getMinScoreBuilder() != null && getMinScoreBuilder().getScore() != null) || (getMaxScoreBuilder() != null && getMaxScoreBuilder().getScore() != null)) { OutcomeDeclaration outcomeDeclaration = AssessmentItemFactory .createOutcomeDeclarationForScore(assessmentItem); outcomeDeclarations.add(outcomeDeclaration); } if(getMinScoreBuilder() != null && getMinScoreBuilder().getScore() != null) { OutcomeDeclaration outcomeDeclaration = AssessmentItemFactory .createOutcomeDeclarationForMinScore(assessmentItem, getMinScoreBuilder().getScore().doubleValue()); outcomeDeclarations.add(outcomeDeclaration); //ensure that the score is not smaller than min. score value ResponseRule minScoreBoundResponseRule = AssessmentItemFactory .createMinScoreBoundLimitRule(assessmentItem.getResponseProcessing()); responseRules.add(minScoreBoundResponseRule); } if(getMaxScoreBuilder() != null && getMaxScoreBuilder().getScore() != null) { OutcomeDeclaration outcomeDeclaration = AssessmentItemFactory .createOutcomeDeclarationForMaxScore(assessmentItem, getMaxScoreBuilder().getScore().doubleValue()); outcomeDeclarations.add(outcomeDeclaration); //ensure that the score is not bigger than the max. score value ResponseRule maxScoreBoundResponseRule = AssessmentItemFactory .createMaxScoreBoundLimitRule(assessmentItem.getResponseProcessing()); responseRules.add(maxScoreBoundResponseRule); } } }