/**
* OLAT - Online Learning and Training<br>
* http://www.olat.org
* <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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <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>
* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
* University of Zurich, Switzerland.
* <p>
*/
package org.olat.ims.qti.editor.beecom.objects;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import org.dom4j.Element;
import org.olat.ims.qti.editor.QTIEditHelper;
import org.olat.ims.qti.editor.beecom.parser.ParserManager;
/**
* @author rkulow
*
*/
public class FIBQuestion extends Question implements QTIObject {
private static ParserManager parserManager = new ParserManager();
public FIBQuestion() {
super();
setType(Question.TYPE_FIB);
}
/**
* Called by ItemParser
*
* @param item
* @return
*/
public static FIBQuestion getInstance(Element item) {
FIBQuestion instance = new FIBQuestion();
Element presentationXML = item.element("presentation");
List elementsXML = presentationXML.element("flow").elements();
List responses = instance.getResponses();
Element el_resprocessing = item.element("resprocessing");
for(Iterator i = elementsXML.iterator(); i.hasNext();) {
Element content = (Element)i.next();
FIBResponse fibresponse = new FIBResponse();
String name = content.getName();
if(name.equalsIgnoreCase("material")) {
Material mat = (Material)parserManager.parse(content);
if (mat != null)
fibresponse.setContent(mat);
fibresponse.setType(FIBResponse.TYPE_CONTENT);
responses.add(fibresponse);
} else if (name.equalsIgnoreCase("response_str")) {
String ident = content.attribute("ident").getValue();
Element render_fib = content.element("render_fib");
content.element("render_fib").element("flow_label");
fibresponse.setType(FIBResponse.TYPE_BLANK);
fibresponse.setIdent(ident);
fibresponse.setSizeFromColumns(render_fib.attribute("columns"));
fibresponse.setMaxLengthFromMaxChar(render_fib.attribute("maxchars"));
List el_varequals = el_resprocessing.selectNodes(".//varequal[@respident='" + ident + "']");
List processedSolutions = new ArrayList(); // list of already process strings
if (el_varequals != null) {
String correctBlank = "";
String correctBlankCaseAttribute = "No";
for (Iterator iter = el_varequals.iterator(); iter.hasNext();) {
Element el_varequal = (Element)iter.next();
String solution = el_varequal.getTextTrim();
if (!processedSolutions.contains(solution)) {
// Solutions are there twice because of the mastery feedback.
// Only process solutions once.
correctBlank = correctBlank + solution;
if (iter.hasNext()) correctBlank = correctBlank + ";";
correctBlankCaseAttribute = el_varequal.attributeValue("case");
processedSolutions.add(solution);
}
}
if (correctBlank.endsWith(";")) correctBlank = correctBlank.substring(0, correctBlank.length() - 1);
fibresponse.setCorrectBlank(correctBlank);
fibresponse.setCaseSensitive(correctBlankCaseAttribute);
}
responses.add(fibresponse);
}
}
Element resprocessingXML = item.element("resprocessing");
if (resprocessingXML != null) {
List respconditions = resprocessingXML.elements("respcondition");
HashMap points = QTIEditHelper.fetchPoints(respconditions, instance.getType());
// postprocessing choices
for(Iterator i = responses.iterator(); i.hasNext();) {
FIBResponse fibResp = (FIBResponse)i.next();
Float fPoints = (Float)points.get(fibResp.getIdent());
if (fPoints != null) {
fibResp.setPoints(fPoints.floatValue());
fibResp.setCorrect(true);
}
}
// if does not contain any ANDs, assume only one combination
// of answers is possible (which sets points by a setvar action="Set")
if (resprocessingXML.selectNodes(".//setvar[@action='Add']").size() == 0) {
instance.setSingleCorrect(true);
Collection values = points.values();
if (values.size() > 0)
instance.setSingleCorrectScore(((Float)(values.iterator().next())).floatValue());
} else {
instance.setSingleCorrect(false);
}
// set min/max score
QTIEditHelper.configureMinMaxScore(instance,
(Element)resprocessingXML.selectSingleNode(".//decvar"));
}
return instance;
}
/**
* Render XML
*/
public void addToElement(Element root) {
Element presentationXML = root.addElement("presentation");
presentationXML.addAttribute("label", "notset");
// presentation
Element flowXML = presentationXML.addElement("flow");
for (Iterator i = getResponses().iterator(); i.hasNext();) {
FIBResponse fibcontent = (FIBResponse) i.next();
if (fibcontent.getType().equals(FIBResponse.TYPE_CONTENT)) {
fibcontent.getContent().addToElement(flowXML);
} else if (fibcontent.getType().equals(FIBResponse.TYPE_BLANK)) {
Element response_str = flowXML.addElement("response_str");
response_str.addAttribute("ident", fibcontent.getIdent());
response_str.addAttribute("rcardinality", "Single");
Element render_fib = response_str.addElement("render_fib");
render_fib.addAttribute("columns", String.valueOf(fibcontent.getSize()));
render_fib.addAttribute("maxchars", String.valueOf(fibcontent.getMaxLength()));
Element flow_label = render_fib.addElement("flow_label");
flow_label.addAttribute("class", "Block");
Element response_lable = flow_label.addElement("response_label");
response_lable.addAttribute("ident", fibcontent.getIdent());
response_lable.addAttribute("rshuffle", "Yes");
}
}
// resprocessing
Element resprocessingXML = root.addElement("resprocessing");
// outcomes
Element decvar =
resprocessingXML.addElement("outcomes").addElement("decvar");
decvar.addAttribute("varname", "SCORE");
decvar.addAttribute("vartype", "Decimal");
decvar.addAttribute("defaultval", "0");
decvar.addAttribute("minvalue", "" + getMinValue());
float maxScore = QTIEditHelper.calculateMaxScore(this);
maxScore = maxScore > getMaxValue() ? getMaxValue() : maxScore;
decvar.addAttribute("maxvalue", "" + maxScore);
decvar.addAttribute("cutvalue", "" + maxScore);
//respcondition
// correct
if (isSingleCorrect()){
buildRespconditionFIBSingle(resprocessingXML);
buildRespcondition_fail(resprocessingXML, true);
} else {
buildRespconditionFIBMulti(resprocessingXML);
buildRespcondition_fail(resprocessingXML, false);
}
// hint
if (getHintText() != null)
QTIEditHelper.addHintElement(root, getHintText());
// solution
if (getSolutionText() != null)
QTIEditHelper.addSolutionElement(root, getSolutionText());
// Feedback for all other cases eg. none has been answered at all
Element incorrect = resprocessingXML.addElement("respcondition");
incorrect.addAttribute("title", "Fail");
incorrect.addAttribute("continue", "Yes");
incorrect.addElement("conditionvar").addElement("other");
Element setvar = incorrect.addElement("setvar");
setvar.addAttribute("varname", "SCORE");
setvar.addAttribute("action", "Set");
setvar.setText("0");
QTIEditHelper.addFeedbackFail(incorrect);
QTIEditHelper.addFeedbackHint(incorrect);
QTIEditHelper.addFeedbackSolution(incorrect);
}
/**
* Build mastery respcondition for FIB in all-banks-must-be-correct mode. Adds
* one respcondition in which all blanks must be answered correctly. This
* respcondition uses the mastery feedback.
* @param resprocessingXML
*/
private void buildRespconditionFIBSingle(Element resprocessingXML) {
Element correct = resprocessingXML.addElement("respcondition");
correct.addAttribute("title", "Mastery");
correct.addAttribute("continue", "Yes");
Element conditionvar = correct.addElement("conditionvar");
Element and = conditionvar.addElement("and");
for (Iterator i = getResponses().iterator(); i.hasNext();) {
FIBResponse fib = (FIBResponse) i.next();
if (fib.getType().equals(FIBResponse.TYPE_BLANK)) {
String[] correctFIBs = fib.getCorrectBlank().split(";");
Element or = and.addElement("or");
for (int j = 0; j < correctFIBs.length; j++) {
Element varequal = or.addElement("varequal");
varequal.addAttribute("respident", fib.getIdent());
varequal.addAttribute("case", fib.getCaseSensitive());
if (correctFIBs[j].length() > 0)
varequal.addCDATA(correctFIBs[j]);
}
}
}
Element setvar = correct.addElement("setvar");
setvar.addAttribute("varname", "SCORE");
setvar.addAttribute("action", "Set");
setvar.setText("" + getSingleCorrectScore());
// Use mastery feedback
QTIEditHelper.addFeedbackMastery(correct);
// remove whole respcondition if empty
if (and.element("or") == null)
resprocessingXML.remove(correct);
}
/**
* Build mastery respconditions for FIB in points-per-blank mode. Adds respconditions
* for every single blank and a respcondition in case of all blanks answered
* correctly that uses the mastery feedback.
* @param resprocessingXML
*/
private void buildRespconditionFIBMulti(Element resprocessingXML) {
for (Iterator i = getResponses().iterator(); i.hasNext();) {
FIBResponse fib = (FIBResponse) i.next();
if (!fib.getType().equals(FIBResponse.TYPE_BLANK)) continue;
float points = fib.getPoints();
if (points == 0) continue;
Element correct = resprocessingXML.addElement("respcondition");
correct.addAttribute("continue", "Yes");
if (points > 0)
correct.addAttribute("title", "Mastery");
else
//doesn't make much sense, but maybe the user has some fancy
// ideas...
correct.addAttribute("title", "Fail");
Element or = correct.addElement("conditionvar").addElement("or");
String[] correctFIBs = fib.getCorrectBlank().split(";");
for (int j = 0; j < correctFIBs.length; j++) {
Element varequal = or.addElement("varequal");
varequal.addAttribute("respident", fib.getIdent());
varequal.addAttribute("case", fib.getCaseSensitive());
if (correctFIBs[j].length() > 0)
varequal.addCDATA(correctFIBs[j]);
}
Element setvar = correct.addElement("setvar");
setvar.addAttribute("varname", "SCORE");
setvar.addAttribute("action", "Add");
setvar.setText("" + points);
}
// Resp condition for feedback mastery:
// all response with points>0 must be selected
Element respcondition_correct = resprocessingXML.addElement("respcondition");
respcondition_correct.addAttribute("title", "Mastery");
respcondition_correct.addAttribute("continue", "Yes");
Element conditionvar = respcondition_correct.addElement("conditionvar");
Element and = conditionvar.addElement("and");
for (Iterator i = getResponses().iterator(); i.hasNext();) {
FIBResponse tmpResponse = (FIBResponse) i.next();
if (!tmpResponse.getType().equals(FIBResponse.TYPE_BLANK)) continue;
String[] correctFIBs = tmpResponse.getCorrectBlank().split(";");
Element or = and.addElement("or");
for (int j = 0; j < correctFIBs.length; j++) {
Element varequal = or.addElement("varequal");
varequal.addAttribute("respident", tmpResponse.getIdent());
varequal.addAttribute("case", tmpResponse.getCaseSensitive());
if (correctFIBs[j].length() > 0)
varequal.addCDATA(correctFIBs[j]);
} // for loop
} // for loop
// Use mastery feedback
QTIEditHelper.addFeedbackMastery(respcondition_correct);
// remove whole respcondition if empty
if (and.element("or") == null)
resprocessingXML.remove(respcondition_correct);
}
/**
* Build fail resprocessing: Adjust score to 0 (if single correct mode)
* and set hints, solutions and fail feedback when any blank is answered
* wrong
*
* @param resprocessingXML
* @param isSingleCorrect
*/
private void buildRespcondition_fail(Element resprocessingXML, boolean isSingleCorrect) {
// build
Element respcondition_fail = resprocessingXML.addElement("respcondition");
respcondition_fail.addAttribute("title", "Fail");
respcondition_fail.addAttribute("continue", "Yes");
Element conditionvar = respcondition_fail.addElement("conditionvar");
Element or = conditionvar.addElement("or");
for (Iterator i = getResponses().iterator(); i.hasNext();) {
FIBResponse tmpResponse = (FIBResponse) i.next();
if (!tmpResponse.getType().equals(FIBResponse.TYPE_BLANK)) continue;
Element not = or.addElement("not");
Element varequal = not.addElement("varequal");
varequal.addAttribute("respident", tmpResponse.getIdent());
varequal.addAttribute("case", tmpResponse.getCaseSensitive());
varequal.setText(tmpResponse.getCorrectBlank());
} // for loop
if (isSingleCorrect){
Element setvar = respcondition_fail.addElement("setvar");
setvar.addAttribute("varname", "SCORE");
setvar.addAttribute("action", "Set");
setvar.addText("0");
}
// Use fail feedback, hints and solutions
QTIEditHelper.addFeedbackFail(respcondition_fail);
QTIEditHelper.addFeedbackHint(respcondition_fail);
QTIEditHelper.addFeedbackSolution(respcondition_fail);
// remove whole respcondition if empty
if (or.element("varequal") == null)
resprocessingXML.remove(respcondition_fail);
}
}