org.olat.ims.qti.editor.beecom.objects.FIBQuestion.java Source code

Java tutorial

Introduction

Here is the source code for org.olat.ims.qti.editor.beecom.objects.FIBQuestion.java

Source

/**
 * 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(final Element item) {
        final FIBQuestion instance = new FIBQuestion();

        final Element presentationXML = item.element("presentation");
        final List elementsXML = presentationXML.element("flow").elements();
        final List responses = instance.getResponses();
        final Element el_resprocessing = item.element("resprocessing");

        for (final Iterator i = elementsXML.iterator(); i.hasNext();) {
            final Element content = (Element) i.next();
            final FIBResponse fibresponse = new FIBResponse();
            final String name = content.getName();
            if (name.equalsIgnoreCase("material")) {
                final 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")) {
                final String ident = content.attribute("ident").getValue();
                final 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"));
                final List el_varequals = el_resprocessing.selectNodes(".//varequal[@respident='" + ident + "']");
                final List processedSolutions = new ArrayList(); // list of already process strings
                if (el_varequals != null) {
                    String correctBlank = "";
                    String correctBlankCaseAttribute = "No";
                    for (final Iterator iter = el_varequals.iterator(); iter.hasNext();) {
                        final Element el_varequal = (Element) iter.next();
                        final 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);
            }
        }

        final Element resprocessingXML = item.element("resprocessing");
        if (resprocessingXML != null) {

            final List respconditions = resprocessingXML.elements("respcondition");
            final HashMap points = QTIEditHelper.fetchPoints(respconditions, instance.getType());

            // postprocessing choices
            for (final Iterator i = responses.iterator(); i.hasNext();) {
                final FIBResponse fibResp = (FIBResponse) i.next();
                final 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);
                final 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(final Element root) {
        final Element presentationXML = root.addElement("presentation");
        presentationXML.addAttribute("label", "notset");
        // presentation
        final Element flowXML = presentationXML.addElement("flow");
        for (final Iterator i = getResponses().iterator(); i.hasNext();) {
            final FIBResponse fibcontent = (FIBResponse) i.next();
            if (fibcontent.getType().equals(FIBResponse.TYPE_CONTENT)) {
                final Material mat = fibcontent.getContent();
                if (mat.getElements().isEmpty()) {
                    // the flow cannot be empty -> add dummy material element
                    flowXML.addElement("material").addElement("mattext").addCDATA("");
                } else {
                    mat.addToElement(flowXML);
                }
            } else if (fibcontent.getType().equals(FIBResponse.TYPE_BLANK)) {
                final Element response_str = flowXML.addElement("response_str");
                response_str.addAttribute("ident", fibcontent.getIdent());
                response_str.addAttribute("rcardinality", "Single");

                final Element render_fib = response_str.addElement("render_fib");
                render_fib.addAttribute("columns", String.valueOf(fibcontent.getSize()));
                render_fib.addAttribute("maxchars", String.valueOf(fibcontent.getMaxLength()));

                final Element flow_label = render_fib.addElement("flow_label");
                flow_label.addAttribute("class", "Block");
                final Element response_lable = flow_label.addElement("response_label");
                response_lable.addAttribute("ident", fibcontent.getIdent());
                response_lable.addAttribute("rshuffle", "Yes");
            }
        }

        // resprocessing
        final Element resprocessingXML = root.addElement("resprocessing");

        // outcomes
        final 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
        final Element incorrect = resprocessingXML.addElement("respcondition");
        incorrect.addAttribute("title", "Fail");
        incorrect.addAttribute("continue", "Yes");
        incorrect.addElement("conditionvar").addElement("other");
        final 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(final Element resprocessingXML) {
        final Element correct = resprocessingXML.addElement("respcondition");
        correct.addAttribute("title", "Mastery");
        correct.addAttribute("continue", "Yes");

        final Element conditionvar = correct.addElement("conditionvar");
        final Element and = conditionvar.addElement("and");
        for (final Iterator i = getResponses().iterator(); i.hasNext();) {
            final FIBResponse fib = (FIBResponse) i.next();
            if (fib.getType().equals(FIBResponse.TYPE_BLANK)) {
                final String[] correctFIBs = fib.getCorrectBlank().split(";");
                final Element or = and.addElement("or");
                for (int j = 0; j < correctFIBs.length; j++) {
                    final Element varequal = or.addElement("varequal");
                    varequal.addAttribute("respident", fib.getIdent());
                    varequal.addAttribute("case", fib.getCaseSensitive());
                    if (correctFIBs[j].length() > 0) {
                        varequal.addCDATA(correctFIBs[j]);
                    }
                }
            }
        }

        final 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(final Element resprocessingXML) {
        for (final Iterator i = getResponses().iterator(); i.hasNext();) {
            final FIBResponse fib = (FIBResponse) i.next();
            if (!fib.getType().equals(FIBResponse.TYPE_BLANK)) {
                continue;
            }
            final float points = fib.getPoints();
            if (points == 0) {
                continue;
            }

            final 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");
            }

            final Element or = correct.addElement("conditionvar").addElement("or");
            final String[] correctFIBs = fib.getCorrectBlank().split(";");
            for (int j = 0; j < correctFIBs.length; j++) {
                final Element varequal = or.addElement("varequal");
                varequal.addAttribute("respident", fib.getIdent());
                varequal.addAttribute("case", fib.getCaseSensitive());
                if (correctFIBs[j].length() > 0) {
                    varequal.addCDATA(correctFIBs[j]);
                }
            }
            final 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
        final Element respcondition_correct = resprocessingXML.addElement("respcondition");
        respcondition_correct.addAttribute("title", "Mastery");
        respcondition_correct.addAttribute("continue", "Yes");
        final Element conditionvar = respcondition_correct.addElement("conditionvar");
        final Element and = conditionvar.addElement("and");

        for (final Iterator i = getResponses().iterator(); i.hasNext();) {
            final FIBResponse tmpResponse = (FIBResponse) i.next();
            if (!tmpResponse.getType().equals(FIBResponse.TYPE_BLANK)) {
                continue;
            }
            final String[] correctFIBs = tmpResponse.getCorrectBlank().split(";");
            final Element or = and.addElement("or");
            for (int j = 0; j < correctFIBs.length; j++) {
                final 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(final Element resprocessingXML, final boolean isSingleCorrect) {
        // build
        final Element respcondition_fail = resprocessingXML.addElement("respcondition");
        respcondition_fail.addAttribute("title", "Fail");
        respcondition_fail.addAttribute("continue", "Yes");
        final Element conditionvar = respcondition_fail.addElement("conditionvar");
        final Element or = conditionvar.addElement("or");

        for (final Iterator i = getResponses().iterator(); i.hasNext();) {
            final FIBResponse tmpResponse = (FIBResponse) i.next();
            if (!tmpResponse.getType().equals(FIBResponse.TYPE_BLANK)) {
                continue;
            }
            final Element not = or.addElement("not");
            final Element varequal = not.addElement("varequal");
            varequal.addAttribute("respident", tmpResponse.getIdent());
            varequal.addAttribute("case", tmpResponse.getCaseSensitive());
            varequal.setText(tmpResponse.getCorrectBlank());
        } // for loop

        if (isSingleCorrect) {
            final 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);
        }
    }

}