org.olat.ims.qti.editor.QTIEditHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.olat.ims.qti.editor.QTIEditHelper.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;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.dom4j.Element;
import org.olat.core.gui.translator.Translator;
import org.olat.core.util.CodeHelper;
import org.olat.core.util.vfs.VFSItem;
import org.olat.ims.qti.editor.beecom.objects.Assessment;
import org.olat.ims.qti.editor.beecom.objects.ChoiceQuestion;
import org.olat.ims.qti.editor.beecom.objects.ChoiceResponse;
import org.olat.ims.qti.editor.beecom.objects.Control;
import org.olat.ims.qti.editor.beecom.objects.EssayQuestion;
import org.olat.ims.qti.editor.beecom.objects.EssayResponse;
import org.olat.ims.qti.editor.beecom.objects.FIBQuestion;
import org.olat.ims.qti.editor.beecom.objects.FIBResponse;
import org.olat.ims.qti.editor.beecom.objects.Feedback;
import org.olat.ims.qti.editor.beecom.objects.Item;
import org.olat.ims.qti.editor.beecom.objects.Material;
import org.olat.ims.qti.editor.beecom.objects.Mattext;
import org.olat.ims.qti.editor.beecom.objects.Metadata;
import org.olat.ims.qti.editor.beecom.objects.QTIDocument;
import org.olat.ims.qti.editor.beecom.objects.QTIObject;
import org.olat.ims.qti.editor.beecom.objects.Question;
import org.olat.ims.qti.editor.beecom.objects.Response;
import org.olat.ims.qti.editor.beecom.objects.Section;
import org.olat.ims.qti.editor.beecom.parser.ParserManager;
import org.olat.ims.qti.process.AssessmentInstance;

/**
 * @author rkulow
 */
public class QTIEditHelper {

    private static String EDITOR_IDENT = "QTIEDIT";
    private static String ITEM_TYPE_SC = "SCQ";
    private static String ITEM_TYPE_MC = "MCQ";
    private static String ITEM_TYPE_FIB = "FIB";
    private static String ITEM_TYPE_ESSAY = "ESSAY";
    private static String ITEM_TYPE_KPRIM = "KPRIM";

    private static ParserManager parserManager = new ParserManager();

    /**
     * Counts the number of sections in this assessment.
     * 
     * @param assessment
     * @return Number of sections in this assessment.
     */
    public static int countSections(final Assessment assessment) {
        return assessment.getSections().size();
    }

    /**
     * Counts the number of items in this assessment.
     * 
     * @param assessment
     * @return Number of items in this assessment.
     */
    public static int countItems(final Assessment assessment) {
        int itemCount = 0;
        final Iterator sectionIter = assessment.getSections().iterator();
        while (sectionIter.hasNext()) {
            itemCount += ((Section) sectionIter.next()).getItems().size();
        }
        return itemCount;
    }

    /**
     * Creates an empty assessment
     * 
     * @param title
     * @param type
     * @return Assessment
     */
    public static Assessment createAssessment(final String title, final String type) {
        final Assessment assessment = new Assessment();
        assessment.setIdent(CodeHelper.getGlobalForeverUniqueID());
        assessment.setTitle(title);
        final Metadata meta = new Metadata();
        meta.setField(AssessmentInstance.QMD_LABEL_TYPE, type);
        assessment.setMetadata(meta);
        return assessment;
    }

    /**
     * Creates an empty section
     * 
     * @param trans
     * @return Section
     */
    public static Section createSection(final Translator trans) {
        final Section section = new Section();
        section.setIdent(CodeHelper.getGlobalForeverUniqueID());
        section.setTitle(trans.translate("editor.newsection"));
        return section;
    }

    /**
     * Creates a new Single Choice item
     * 
     * @param trans
     * @return New Singe Choice item.
     */
    public static Item createSCItem(final Translator trans) {
        final Item newItem = new Item();
        newItem.setIdent(EDITOR_IDENT + ":" + ITEM_TYPE_SC + ":" + String.valueOf(CodeHelper.getRAMUniqueID()));
        newItem.setTitle(trans.translate("editor.newquestion"));
        newItem.setLabel("");
        // conrols
        final Control control = new Control();
        final ArrayList controls = new ArrayList();
        controls.add(control);
        newItem.setItemcontrols(controls);

        // pepare question
        final ChoiceQuestion question = new ChoiceQuestion();
        question.setLable(trans.translate("editor.newquestion"));
        question.getQuestion().getElements().add(new Mattext(trans.translate("editor.newquestiontext")));
        question.setType(Question.TYPE_SC);
        question.setSingleCorrect(true);
        question.setSingleCorrectScore(1);

        final ChoiceResponse newChoice = new ChoiceResponse();
        newChoice.setCorrect(true);
        newChoice.getContent().add(new Mattext(trans.translate("editor.newresponsetext")));
        question.getResponses().add(newChoice);

        QTIEditHelper.setFeedbackMastery(newItem, "");
        QTIEditHelper.setFeedbackFail(newItem, "");

        newItem.setQuestion(question);
        return newItem;
    }

    /**
     * Creates a new Multiple Choice item.
     * 
     * @param trans
     * @return New Multiple Choice item.
     */
    public static Item createMCItem(final Translator trans) {
        // create item
        final Item newItem = new Item();
        newItem.setIdent(EDITOR_IDENT + ":" + ITEM_TYPE_MC + ":" + String.valueOf(CodeHelper.getRAMUniqueID()));
        newItem.setTitle(trans.translate("editor.newquestion"));
        newItem.setLabel("");

        // conrols
        final Control control = new Control();
        final ArrayList controls = new ArrayList();
        controls.add(control);
        newItem.setItemcontrols(controls);

        // pepare question
        final ChoiceQuestion question = new ChoiceQuestion();
        question.setLable(trans.translate("editor.newquestion"));
        question.getQuestion().getElements().add(new Mattext(trans.translate("editor.newquestiontext")));
        question.setType(Question.TYPE_MC);
        question.setSingleCorrect(true);
        question.setSingleCorrectScore(1);

        final ChoiceResponse newChoice = new ChoiceResponse();
        newChoice.getContent().add(new Mattext(trans.translate("editor.newresponsetext")));
        newChoice.setCorrect(true);
        newChoice.setPoints(1);
        question.getResponses().add(newChoice);
        newItem.setQuestion(question);

        QTIEditHelper.setFeedbackMastery(newItem, "");
        QTIEditHelper.setFeedbackFail(newItem, "");

        return newItem;
    }

    /**
     * Creates a new Kprim item
     * 
     * @param trans
     * @return New Kprim item.
     */
    public static Item createKPRIMItem(final Translator trans) {
        // create item
        final Item newItem = new Item();
        newItem.setIdent(EDITOR_IDENT + ":" + ITEM_TYPE_KPRIM + ":" + String.valueOf(CodeHelper.getRAMUniqueID()));
        newItem.setTitle(trans.translate("editor.newquestion"));
        newItem.setLabel("");

        // controls
        final Control control = new Control();
        final ArrayList controls = new ArrayList();
        controls.add(control);
        newItem.setItemcontrols(controls);

        // prepare question
        final float maxValue = 1;
        final ChoiceQuestion question = new ChoiceQuestion();
        question.setLable(trans.translate("editor.newquestion"));
        question.getQuestion().getElements().add(new Mattext(trans.translate("editor.newquestiontext")));
        question.setType(Question.TYPE_KPRIM);
        question.setSingleCorrect(false);

        // Kprim has always 4 answers, each of them score 1/4 of the maximum value
        final ChoiceResponse newChoice = new ChoiceResponse();
        newChoice.getContent().add(new Mattext(trans.translate("editor.newresponsetext")));
        newChoice.setCorrect(false);
        newChoice.setPoints(maxValue / 4);
        question.getResponses().add(newChoice);
        final ChoiceResponse newChoice2 = new ChoiceResponse();
        newChoice2.getContent().add(new Mattext(trans.translate("editor.newresponsetext")));
        newChoice2.setCorrect(false);
        newChoice2.setPoints(maxValue / 4);
        question.getResponses().add(newChoice2);
        final ChoiceResponse newChoice3 = new ChoiceResponse();
        newChoice3.getContent().add(new Mattext(trans.translate("editor.newresponsetext")));
        newChoice3.setCorrect(false);
        newChoice3.setPoints(maxValue / 4);
        question.getResponses().add(newChoice3);
        final ChoiceResponse newChoice4 = new ChoiceResponse();
        newChoice4.getContent().add(new Mattext(trans.translate("editor.newresponsetext")));
        newChoice4.setCorrect(false);
        newChoice4.setPoints(maxValue / 4);
        question.getResponses().add(newChoice4);
        question.setMaxValue(maxValue);
        newItem.setQuestion(question);

        QTIEditHelper.setFeedbackMastery(newItem, "");
        QTIEditHelper.setFeedbackFail(newItem, "");

        return newItem;
    }

    /**
     * Creates a new FIB item
     * 
     * @param trans
     * @return New fib item.
     */
    public static Item createFIBItem(final Translator trans) {
        // create item
        final Item newItem = new Item();
        newItem.setIdent(EDITOR_IDENT + ":" + ITEM_TYPE_FIB + ":" + String.valueOf(CodeHelper.getRAMUniqueID()));
        newItem.setTitle(trans.translate("editor.newquestion"));
        newItem.setLabel("");

        // conrols
        final Control control = new Control();
        final ArrayList controls = new ArrayList();
        controls.add(control);
        newItem.setItemcontrols(controls);

        QTIEditHelper.setFeedbackMastery(newItem, "");
        QTIEditHelper.setFeedbackFail(newItem, "");

        final FIBQuestion fibquestion = new FIBQuestion();
        fibquestion.getQuestion().getElements().add(new Mattext(trans.translate("editor.newquestiontext")));
        fibquestion.setSingleCorrect(true);
        fibquestion.setSingleCorrectScore(1);

        final FIBResponse response = new FIBResponse();
        response.setType(FIBResponse.TYPE_CONTENT);
        final Material mat = new Material();
        mat.add(new Mattext(trans.translate("editor.newtextelement")));
        response.setContent(mat);
        fibquestion.getResponses().add(response);
        newItem.setQuestion(fibquestion);

        return newItem;
    }

    /**
     * Creates a new essay item
     * 
     * @param trans
     * @return New essay item.
     */
    public static Item createEssayItem(final Translator trans) {
        // create item
        final Item newItem = new Item();
        newItem.setIdent(EDITOR_IDENT + ":" + ITEM_TYPE_ESSAY + ":" + String.valueOf(CodeHelper.getRAMUniqueID()));
        newItem.setTitle(trans.translate("editor.newquestion"));
        newItem.setLabel("");

        // conrols
        final Control control = new Control();
        final ArrayList controls = new ArrayList();
        controls.add(control);
        newItem.setItemcontrols(controls);

        QTIEditHelper.setFeedbackMastery(newItem, "");
        QTIEditHelper.setFeedbackFail(newItem, "");

        final EssayQuestion essayquestion = new EssayQuestion();
        essayquestion.getQuestion().getElements().add(new Mattext(trans.translate("editor.newquestiontext")));
        essayquestion.setSingleCorrect(true);
        essayquestion.setSingleCorrectScore(1);

        final EssayResponse response = new EssayResponse();
        final Material mat = new Material();
        mat.add(new Mattext(trans.translate("editor.newtextelement")));
        response.setContent(mat);
        essayquestion.getResponses().add(response);
        newItem.setQuestion(essayquestion);

        return newItem;
    }

    /**
     * Configure max score for a question.
     * 
     * @param question
     * @param decvar
     */
    public static void configureMinMaxScore(final Question question, final Element decvar) {
        // set min/max score
        boolean doCalculate = true;
        if (decvar != null) {
            final String min = decvar.attributeValue("minvalue");
            if (min != null) {
                question.setMinValue(min);
            }
            final String max = decvar.attributeValue("maxvalue");
            if (max != null) {
                question.setMaxValue(max);
                doCalculate = false;
            }
        }
        if (doCalculate) {
            question.setMaxValue(QTIEditHelper.calculateMaxScore(question));
        }
    }

    /**
     * Get controls.
     * 
     * @param object
     * @return Controls.
     */
    public static Control getControl(final QTIObject object) {
        Control control = null;
        List controls = null;
        if (Item.class.isAssignableFrom(object.getClass())) {
            final Item item = (Item) object;
            controls = item.getItemcontrols();
        } else if (Section.class.isAssignableFrom(object.getClass())) {
            final Section section = (Section) object;
            controls = section.getSectioncontrols();
        } else if (Assessment.class.isAssignableFrom(object.getClass())) {
            final Assessment assessment = (Assessment) object;
            controls = assessment.getAssessmentcontrols();
        }
        for (final Iterator i = controls.iterator(); i.hasNext();) {
            final Control tmp = (Control) i.next();
            if (tmp.getView() != null) {
                if (tmp.getView().equalsIgnoreCase("all")) {
                    control = tmp;
                    break;
                }
            } else {
                control = tmp;
            }
        }
        return control;
    }

    /**
     * Calculates the max score for a question (sum of scores)
     * 
     * @param question
     * @return max score.
     */
    public static float calculateMaxScore(final Question question) {
        float tmpScore = 0;
        if (question.isSingleCorrect()) {
            return question.getSingleCorrectScore();
        }
        for (final Iterator iter = question.getResponses().iterator(); iter.hasNext();) {
            final Response resp = (Response) iter.next();
            final float points = resp.getPoints();
            if (points > 0) {
                tmpScore = tmpScore + points;
            }
        }
        return tmpScore;
    }

    /**
     * Returns a hasmap with responselabel_idents as keys and points as values.
     * 
     * @param respconditions
     * @param type
     * @return hasmap with responselabel_idents as keys and points as values.
     */
    public static HashMap fetchPoints(final List respconditions, final int type) {
        final HashMap points = new HashMap();
        for (final Iterator i = respconditions.iterator(); i.hasNext();) {
            final Element el_resp_condition = (Element) i.next();
            // /todo
            float fPoints = 0;
            try {
                final Element el_setvar = el_resp_condition.element("setvar");
                if (el_setvar == null) {
                    continue;
                }
                if (!el_setvar.attributeValue("action").equals("Add")
                        && !el_setvar.attributeValue("action").equals("Subtract")
                        && !el_setvar.attributeValue("action").equals("Set")) {
                    continue;
                }
                fPoints = new Float(el_setvar.getTextTrim()).floatValue();
                if (el_setvar.attributeValue("action").equals("Subtract")) {
                    fPoints = fPoints * -1;
                }
            } catch (final NumberFormatException nfe) {
                continue;
            }
            if (fPoints != 0) {
                final Element conditionvar = el_resp_condition.element("conditionvar");
                final Element and = conditionvar.element("and");
                // in and are all choices that are true

                final List tmp_points = (and == null) ? conditionvar.selectNodes(".//varequal")
                        : and.selectNodes(".//varequal");
                for (final Iterator iter = tmp_points.iterator(); iter.hasNext();) {
                    final Element el_varequal = (Element) iter.next();
                    if (type == Question.TYPE_SC || type == Question.TYPE_MC || type == Question.TYPE_KPRIM) {
                        points.put(el_varequal.getTextTrim(), new Float(fPoints));
                    } else if (type == Question.TYPE_FIB) {
                        points.put(el_varequal.attributeValue("respident"), new Float(fPoints));
                    }
                }
            }
        }
        return points;
    }

    /**
     * Fetch choices.
     * 
     * @param response_labels
     * @return Map of choices.
     */
    public static List fetchChoices(final List response_labels) {
        final List choices = new ArrayList();
        for (final Iterator i = response_labels.iterator(); i.hasNext();) {
            final ChoiceResponse choice = new ChoiceResponse();
            final Element response_label = (Element) i.next();
            choice.setIdent(response_label.attributeValue("ident"));

            final List materials = response_label.selectNodes(".//material");
            final Material content = new Material();
            for (final Iterator iter = materials.iterator(); iter.hasNext();) {
                final Element el_material = (Element) iter.next();
                final Material mat = (Material) parserManager.parse(el_material);
                content.getElements().addAll(mat.getElements());
            }
            // assure material always has some content
            if (content.getElements().size() == 0) {
                content.getElements().add(new Mattext("[blank]"));
            }
            choice.setContent(content);
            choices.add(choice);
        }
        return choices;
    }

    /**
     * Get olat response feddback
     * 
     * @param object
     * @param respident
     * @return feedback
     */
    public static String getFeedbackOlatRespText(final QTIObject object, final String respident) {
        return getFeedbackText(object, respident);
    }

    /**
     * Get olat response feddback
     * 
     * @param object
     * @param respident
     * @return feedback
     */
    public static Material getFeedbackOlatRespMaterial(final QTIObject object, final String respident) {
        return getFeedbackMaterial(object, respident);
    }

    /**
     * Get mastery feedback
     * 
     * @param object
     * @return mastery feedback
     */
    public static String getFeedbackMasteryText(final QTIObject object) {
        return getFeedbackText(object, "Mastery");
    }

    /**
     * Get mastery feedback
     * 
     * @param object
     * @return mastery feedback
     */
    public static Material getFeedbackMasteryMaterial(final QTIObject object) {
        return getFeedbackMaterial(object, "Mastery");
    }

    /**
     * Get fail feedback
     * 
     * @param object
     * @return fail feedback
     */
    public static String getFeedbackFailText(final QTIObject object) {
        return getFeedbackText(object, "Fail");
    }

    /**
     * Get fail feedback
     * 
     * @param object
     * @return fail feedback
     */
    public static Material getFeedbackFailMaterial(final QTIObject object) {
        return getFeedbackMaterial(object, "Fail");
    }

    /**
     * Get feedback
     * 
     * @param object
     * @param sIdent
     * @return feedback
     */
    public static String getFeedbackText(final QTIObject object, final String sIdent) {
        final Feedback feedback = getFeedback(object, sIdent);
        try {
            final Material mat = (Material) feedback.getMaterials().get(0);
            return mat.renderAsText();
        } catch (final Exception e) {
            //
        }
        return "";
    }

    /**
     * @param object
     * @param sIdent
     * @return
     */
    public static Feedback getFeedback(final QTIObject object, final String sIdent) {
        final List<Feedback> feedbacks = getFeedbacks(object);
        return getFeedback(sIdent, feedbacks);
    }

    /**
     * @param object
     * @param sIdent
     * @return
     */
    public static Material getFeedbackMaterial(final QTIObject object, final String sIdent) {
        final Feedback feedback = getFeedback(object, sIdent);
        Material mat = null;
        try {
            mat = (Material) feedback.getMaterials().get(0);
        } catch (final NullPointerException e) {
            // feedback is null
        }
        return mat;
    }

    /**
     * @param sIdent
     * @param feedback
     * @param feedbacks
     * @return
     */
    private static Feedback getFeedback(final String sIdent, final List<Feedback> feedbacks) {
        Feedback feedback = null;
        for (final Feedback tmp : feedbacks) {
            if (tmp.getIdent().equalsIgnoreCase(sIdent)) {
                if (tmp.getView() != null) {
                    if (tmp.getView().equalsIgnoreCase("all")) {
                        feedback = tmp;
                        break;
                    }
                } else {
                    feedback = tmp;
                }
            }
        }
        return feedback;
    }

    /**
     * @param object
     * @return
     */
    private static List<Feedback> getFeedbacks(final QTIObject object) {
        List<Feedback> feedbacks = null;
        if (Item.class.isAssignableFrom(object.getClass())) {
            final Item item = (Item) object;
            feedbacks = item.getItemfeedbacks();
        } else if (Section.class.isAssignableFrom(object.getClass())) {
            final Section section = (Section) object;
            feedbacks = section.getSectionfeedbacks();
        } else if (Assessment.class.isAssignableFrom(object.getClass())) {
            final Assessment assessment = (Assessment) object;
            feedbacks = assessment.getAssessfeedbacks();
        }
        return feedbacks;
    }

    /**
     * Set response feedback
     * 
     * @param object
     * @param feedbackString
     * @param respident
     */
    public static void setFeedbackOlatResp(final QTIObject object, final String feedbackString,
            final String respident) {
        setFeedback(object, feedbackString, respident);
    }

    /**
     * Set mastery feedback.
     * 
     * @param object
     * @param feedbackString
     */
    public static void setFeedbackMastery(final QTIObject object, final String feedbackString) {
        setFeedback(object, feedbackString, "Mastery");
    }

    /**
     * Set fail feedback.
     * 
     * @param object
     * @param feedbackString
     */
    public static void setFeedbackFail(final QTIObject object, final String feedbackString) {
        setFeedback(object, feedbackString, "Fail");
    }

    /**
     * Set feedback
     * 
     * @param object
     * @param feedbackString
     * @param sIdent
     */
    public static void setFeedback(final QTIObject object, String feedbackString, final String sIdent) {
        final List feedbacks = getFeedbacks(object);
        final Feedback feedback = getFeedback(sIdent, feedbacks);

        if (feedbackString == null || feedbackString.trim().length() == 0) {
            feedbacks.remove(feedback);
            return;
        }

        if (feedback != null) {
            feedbackString = feedbackString.trim();
            final List matList = feedback.getMaterials();
            if (matList.size() > 0) {
                Material mat = (Material) feedback.getMaterials().get(0);
                if (mat == null) {
                    mat = new Material();
                    mat.getElements().add(new Mattext(feedbackString));
                    feedback.getMaterials().add(mat);
                } else if (mat.getElements().size() > 0) {
                    mat.getElements().set(0, new Mattext(feedbackString));
                } else {
                    mat.getElements().add(new Mattext(feedbackString));
                }
            } else {
                final Material mat = new Material();
                mat.getElements().add(new Mattext(feedbackString));
                feedback.getMaterials().add(mat);
            }
        } else {
            final Feedback newFeedback = new Feedback();
            newFeedback.setIdent(sIdent);
            newFeedback.setView("All");
            final Mattext newMattext = new Mattext(feedbackString);

            final ArrayList newMattextL = new ArrayList();
            newMattextL.add(newMattext);

            final Material material = new Material();
            material.setElements(newMattextL);

            final ArrayList newMaterialL = new ArrayList();
            newMaterialL.add(material);
            newFeedback.setMaterials(newMaterialL);
            feedbacks.add(newFeedback);
        }

    }

    /**
     * Add objectives.
     * 
     * @param root
     * @param objectives
     */
    public static void addObjectives(final Element root, final String objectives) {
        if (objectives != null && objectives.length() > 0) {
            final Element mattext = root.addElement("objectives").addElement("material").addElement("mattext");
            mattext.addCDATA(objectives);
        }
    }

    /**
     * Add response feedback.
     * 
     * @param root
     * @param respident
     */
    public static void addFeedbackOlatResp(final Element root, final String respident) {
        addFeedback(root, "Response", respident);
    }

    /**
     * Add mastery feedback
     * 
     * @param root
     */
    public static void addFeedbackMastery(final Element root) {
        addFeedback(root, "Response", "Mastery");
    }

    /**
     * Add fail feedback
     * 
     * @param root
     */
    public static void addFeedbackFail(final Element root) {
        addFeedback(root, "Response", "Fail");
    }

    /**
     * Add hint
     * 
     * @param root
     */
    public static void addFeedbackHint(final Element root) {
        addFeedback(root, "Solution", "Solution");
    }

    /**
     * Add solution
     * 
     * @param root
     */
    public static void addFeedbackSolution(final Element root) {
        addFeedback(root, "Hint", "Hint");
    }

    private static void addFeedback(final Element root, final String feedbacktype, final String linkrefid) {
        final Element displayfeedback = root.addElement("displayfeedback");
        displayfeedback.addAttribute("feedbacktype", feedbacktype);
        displayfeedback.addAttribute("linkrefid", linkrefid);
    }

    /**
     * Add solution
     * 
     * @param root
     * @param solutionText
     */
    public static void addSolutionElement(final Element root, final String solutionText) {
        final Element el_solution = root.addElement("itemfeedback");
        el_solution.addAttribute("ident", "Solution");
        el_solution.addAttribute("view", "All");
        el_solution.addElement("solution").addElement("solutionmaterial").addElement("material")
                .addElement("mattext").addCDATA(solutionText);
    }

    /**
     * Add hint
     * 
     * @param root
     * @param hintText
     */
    public static void addHintElement(final Element root, final String hintText) {
        final Element el_feedback = root.addElement("itemfeedback");
        el_feedback.addAttribute("ident", "Hint");
        el_feedback.addAttribute("view", "All");
        final Element el_hint = el_feedback.addElement("hint");
        el_hint.addAttribute("feedbackstyle", "Incremental");
        el_hint.addElement("hintmaterial").addElement("material").addElement("mattext").addCDATA(hintText);
    }

    /**
     * Retrieves all deleteable media files, that is the media files that are referenced by thisItem only. Note: doesn't retrieve the media from the question description
     * because of OLAT-4647
     * 
     * @param qtiDocument
     * @param thisItem
     * @return
     */
    public static Set<String> getDeletableMedia(final QTIDocument qtiDocument, final Item thisItem) {
        final Set<String> deletableMediaFiles = QTIEditHelper.getMediaReferencesForItem(qtiDocument, thisItem);
        if (deletableMediaFiles.size() > 0) {
            final Set<String> referencedMediaFiles = QTIEditHelper.getMediaReferencesExceptForItem(qtiDocument,
                    thisItem);
            deletableMediaFiles.removeAll(referencedMediaFiles);
        }
        return deletableMediaFiles;
    }

    /**
     * Retrieves all referenced media by thisItem if filterOut is false, or all referenced media by other items if filterOut is true.
     * <p>
     * Iterates over all sections, items, etc. </br> -> if filterOut is true gets all references except those for thisItem. -> if filterOut is false gets all references
     * for thisItem.
     * 
     * @param qtiDocument
     * @param thisItem
     * @param filterOut
     * @return Returns empty set if no reference found.
     */
    private static Set<String> getMediaReferences(final QTIDocument qtiDocument, final Item thisItem,
            final boolean filterOut) {
        final HashSet<String> returnSet = new HashSet<String>();
        // sections
        final List sectionList = qtiDocument.getAssessment().getSections();
        final Iterator sectionIterator = sectionList.iterator();
        while (sectionIterator.hasNext()) {
            // section
            final Section section = (Section) sectionIterator.next();
            final List itemList = section.getItems();
            final Iterator listIterator = itemList.iterator();
            while (listIterator.hasNext()) {
                // item
                final Item item = (Item) listIterator.next();
                if ((filterOut && thisItem.getIdent().equals(item.getIdent()))
                        || (!filterOut && !thisItem.getIdent().equals(item.getIdent()))) {
                    continue;
                }
                // question
                final Material material = item.getQuestion().getQuestion();
                if (material != null) {
                    final String htmlContent = material.renderAsHtmlForEditor();
                    // parse filenames
                    returnSet.addAll(getMediaFileNames(htmlContent));
                }
                // responses
                final List responseList = item.getQuestion().getResponses();
                final Iterator responseIterator = responseList.iterator();
                while (responseIterator.hasNext()) {
                    final Response response = (Response) responseIterator.next();
                    final Material responseMat = response.getContent();
                    // parse filenames
                    if (responseMat != null) {
                        returnSet.addAll(getMediaFileNames(responseMat.renderAsHtmlForEditor()));
                    }
                    // response-level feedback
                    final Material responseFeedbackMat = QTIEditHelper.getFeedbackOlatRespMaterial(item,
                            response.getIdent());
                    if (responseFeedbackMat != null) {
                        returnSet.addAll(getMediaFileNames(responseFeedbackMat.renderAsHtmlForEditor()));
                    }
                }
                // feedback
                final Material masteryMat = QTIEditHelper.getFeedbackMasteryMaterial(item);
                if (masteryMat != null) {
                    returnSet.addAll(getMediaFileNames(masteryMat.renderAsHtmlForEditor()));
                }
                final Material failureMat = QTIEditHelper.getFeedbackFailMaterial(item);
                if (failureMat != null) {
                    returnSet.addAll(getMediaFileNames(failureMat.renderAsHtmlForEditor()));
                }
            }
        }
        return returnSet;
    }

    /**
     * @param qtiDocument
     * @param thisItem
     * @return Returns a Set with the media file names referenced by thisItem.
     */
    private static Set<String> getMediaReferencesForItem(final QTIDocument qtiDocument, final Item thisItem) {
        return getMediaReferences(qtiDocument, thisItem, false);
    }

    /**
     * @param qtiDocument
     * @param thisItem
     * @return Returns a Set with the media file names referenced by all except thisItem.
     */
    private static Set<String> getMediaReferencesExceptForItem(final QTIDocument qtiDocument, final Item thisItem) {
        return getMediaReferences(qtiDocument, thisItem, true);
    }

    /**
     * Extracts substrings between media/ and next ", and add them to a set. The htmlString contains something like: ... img src=".../media/filename.jpg" ...
     * 
     * @param htmlString
     * @return
     */
    private static Set<String> getMediaFileNames(final String htmlString) {
        final HashSet<String> returnSet = new HashSet<String>();
        String current = htmlString;
        while (current.indexOf("media/") > 0) {
            current = current.substring(current.indexOf("media/") + 6);
            final int position = current.indexOf("\"");
            if (position > 0) {
                final String filename = current.substring(0, position);
                returnSet.add(filename);
                current = current.substring(position + 1, current.length());
            }
        }
        return returnSet;
    }

    /**
     * Deletes the files found in the referencedMediaSet.
     * 
     * @param referencedMediaSet
     * @param allMedia
     */
    public static void removeUnusedMedia(final Set<String> deleteableSet, final List<VFSItem> allMedia) {
        final Iterator<VFSItem> itemIterator = allMedia.iterator();
        while (itemIterator.hasNext()) {
            final VFSItem item = itemIterator.next();
            if (deleteableSet.contains(item.getName())) {
                // System.out.println("Delete unused media file: " + item.getName());
                item.delete();
            }
        }
    }
}