/**
* <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.pool;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.DoubleAdder;
import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import org.apache.commons.lang.StringEscapeUtils;
import org.cyberneko.html.parsers.SAXParser;
import org.olat.core.CoreSpringFactory;
import org.olat.core.gui.translator.Translator;
import org.olat.core.helpers.Settings;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.util.FileUtils;
import org.olat.core.util.StringHelper;
import org.olat.core.util.Util;
import org.olat.core.util.vfs.VFSContainer;
import org.olat.core.util.vfs.VFSItem;
import org.olat.core.util.vfs.VFSLeaf;
import org.olat.ims.qti.QTIModule;
import org.olat.ims.qti.editor.QTIEditHelper;
import org.olat.ims.qti.editor.QTIEditorPackage;
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.Control;
import org.olat.ims.qti.editor.beecom.objects.Duration;
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.FIBResponse;
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.OutcomesProcessing;
import org.olat.ims.qti.editor.beecom.objects.QTIDocument;
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.objects.SelectionOrdering;
import org.olat.ims.qti.fileresource.TestFileResource;
import org.olat.ims.qti.qpool.QTI12HtmlHandler;
import org.olat.ims.qti21.QTI21Constants;
import org.olat.ims.qti21.model.IdentifierGenerator;
import org.olat.ims.qti21.model.xml.AssessmentHtmlBuilder;
import org.olat.ims.qti21.model.xml.AssessmentItemBuilder;
import org.olat.ims.qti21.model.xml.AssessmentItemFactory;
import org.olat.ims.qti21.model.xml.AssessmentTestBuilder;
import org.olat.ims.qti21.model.xml.AssessmentTestFactory;
import org.olat.ims.qti21.model.xml.ManifestBuilder;
import org.olat.ims.qti21.model.xml.ManifestMetadataBuilder;
import org.olat.ims.qti21.model.xml.ModalFeedbackBuilder;
import org.olat.ims.qti21.model.xml.QtiNodesExtractor;
import org.olat.ims.qti21.model.xml.interactions.EssayAssessmentItemBuilder;
import org.olat.ims.qti21.model.xml.interactions.FIBAssessmentItemBuilder;
import org.olat.ims.qti21.model.xml.interactions.FIBAssessmentItemBuilder.EntryType;
import org.olat.ims.qti21.model.xml.interactions.FIBAssessmentItemBuilder.TextEntry;
import org.olat.ims.qti21.model.xml.interactions.KPrimAssessmentItemBuilder;
import org.olat.ims.qti21.model.xml.interactions.MultipleChoiceAssessmentItemBuilder;
import org.olat.ims.qti21.model.xml.interactions.SimpleChoiceAssessmentItemBuilder;
import org.olat.ims.qti21.model.xml.interactions.SimpleChoiceAssessmentItemBuilder.ScoreEvaluation;
import org.olat.ims.qti21.model.xml.interactions.SingleChoiceAssessmentItemBuilder;
import org.olat.modules.qpool.QuestionType;
import org.olat.modules.qpool.manager.QItemTypeDAO;
import org.olat.modules.qpool.model.QuestionItemImpl;
import org.olat.resource.OLATResource;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import de.bps.onyx.plugin.OnyxModule;
import uk.ac.ed.ph.jqtiplus.node.AssessmentObject;
import uk.ac.ed.ph.jqtiplus.node.content.variable.RubricBlock;
import uk.ac.ed.ph.jqtiplus.node.content.xhtml.text.P;
import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem;
import uk.ac.ed.ph.jqtiplus.node.item.interaction.ChoiceInteraction;
import uk.ac.ed.ph.jqtiplus.node.item.interaction.choice.SimpleAssociableChoice;
import uk.ac.ed.ph.jqtiplus.node.item.interaction.choice.SimpleChoice;
import uk.ac.ed.ph.jqtiplus.node.test.AssessmentItemRef;
import uk.ac.ed.ph.jqtiplus.node.test.AssessmentSection;
import uk.ac.ed.ph.jqtiplus.node.test.AssessmentTest;
import uk.ac.ed.ph.jqtiplus.node.test.ControlObject;
import uk.ac.ed.ph.jqtiplus.node.test.ItemSessionControl;
import uk.ac.ed.ph.jqtiplus.node.test.Selection;
import uk.ac.ed.ph.jqtiplus.node.test.TestPart;
import uk.ac.ed.ph.jqtiplus.node.test.TimeLimits;
import uk.ac.ed.ph.jqtiplus.serialization.QtiSerializer;
import uk.ac.ed.ph.jqtiplus.types.Identifier;
import uk.ac.ed.ph.jqtiplus.value.Orientation;
/**
*
* Initial date: 19.02.2016<br>
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*
*/
public class QTI12To21Converter {
private static final OLog log = Tracing.createLoggerFor(QTI12To21Converter.class);
private final Locale locale;
private final File unzippedDirRoot;
private final QtiSerializer qtiSerializer = new QtiSerializer(null);
private final AssessmentHtmlBuilder htmlBuilder = new AssessmentHtmlBuilder(qtiSerializer);
private final ManifestBuilder manifest;
private List<String> materialPath = new ArrayList<>();
private Map<String,String> materialMappings = new HashMap<>();
private List<String> errors = new ArrayList<>();
private final DoubleAdder atomicMaxScore = new DoubleAdder();
public QTI12To21Converter(File unzippedDirRoot, Locale locale) {
this.locale = locale;
this.unzippedDirRoot = unzippedDirRoot;
manifest = ManifestBuilder.createAssessmentTestBuilder();
}
public AssessmentTest convert(QTIEditorPackage qtiEditorPackage)
throws URISyntaxException {
return convert(qtiEditorPackage.getBaseDir(), qtiEditorPackage.getQTIDocument());
}
public AssessmentTest convert(VFSContainer originalContainer, QTIDocument doc)
throws URISyntaxException {
Assessment assessment = doc.getAssessment();
AssessmentTest assessmentTest = new AssessmentTest();
String assessmentTestIdentifier = IdentifierGenerator.newAssessmentTestFilename();
File testFile = new File(unzippedDirRoot, assessmentTestIdentifier + ".xml");
manifest.appendAssessmentTest(testFile.getName());
assessmentTest.setIdentifier(assessmentTestIdentifier);
assessmentTest.setTitle(assessment.getTitle());
assessmentTest.setToolName(QTI21Constants.TOOLNAME);
assessmentTest.setToolVersion(Settings.getVersion());
convertDuration((Duration)assessment.getDuration(), assessmentTest);
TestPart testPart = AssessmentTestFactory.createTestPart(assessmentTest);
ItemSessionControl itemSessionControl = testPart.getItemSessionControl();
Control tmpControl = QTIEditHelper.getControl(assessment);
if(tmpControl.getFeedback() == Control.CTRL_YES) {
itemSessionControl.setShowFeedback(Boolean.TRUE);
}
if(tmpControl.getSolution() == Control.CTRL_YES) {
itemSessionControl.setShowSolution(Boolean.TRUE);
}
AssessmentTestBuilder assessmentTestBuilder = new AssessmentTestBuilder(assessmentTest);
//root
List<Section> sections = assessment.getSections();
for(Section section:sections) {
convert(section, testPart);
}
//this are lost in QTI 2.1
//assessment.getSelection_ordering().getOrderType();
//assessment.getSelection_ordering().getSelectionNumber();
OutcomesProcessing outcomesProcessing = assessment.getOutcomes_processing();
if(outcomesProcessing != null) {
String cutValue = outcomesProcessing.getField(OutcomesProcessing.CUTVALUE);
if(StringHelper.containsNonWhitespace(cutValue)) {
try {
assessmentTestBuilder.setCutValue(Double.valueOf(cutValue));
} catch (NumberFormatException e) {
log.error("Cannot parse cut value: " + cutValue, e);
}
}
}
assessmentTestBuilder.setMaxScore(atomicMaxScore.doubleValue());
assessmentTest = assessmentTestBuilder.build();
persistAssessmentObject(testFile, assessmentTest);
manifest.write(new File(unzippedDirRoot, "imsmanifest.xml"));
copyMaterial(originalContainer);
return assessmentTest;
}
private void convert(Section section, TestPart testPart)
throws URISyntaxException {
AssessmentSection assessmentSection = AssessmentTestFactory.appendAssessmentSection("Section", testPart);
assessmentSection.setTitle(section.getTitle());
convertDuration(section.getDuration(), assessmentSection);
RubricBlock rubricBlock = assessmentSection.getRubricBlocks().get(0);
rubricBlock.getBlocks().clear();
String objectives = section.getObjectives();
htmlBuilder.appendHtml(rubricBlock, blockedHtml(objectives));
boolean shuffle = SelectionOrdering.RANDOM.equals(section.getSelection_ordering().getOrderType());
assessmentSection.getOrdering().setShuffle(shuffle);
int selectionNum = section.getSelection_ordering().getSelectionNumber();
if(selectionNum > 0) {
Selection selection = new Selection(assessmentSection);
selection.setSelect(selectionNum);
assessmentSection.setSelection(selection);
}
List<Item> items = section.getItems();
for(Item item:items) {
AssessmentItemBuilder itemBuilder = null;
if(item != null && item.getQuestion() != null) {
int questionType = item.getQuestion().getType();
switch (questionType) {
case Question.TYPE_SC:
itemBuilder = convertSingleChoice(item);
break;
case Question.TYPE_MC:
itemBuilder = convertMultipleChoice(item);
break;
case Question.TYPE_KPRIM:
itemBuilder = convertKPrim(item);
break;
case Question.TYPE_FIB:
itemBuilder = convertFIB(item);
break;
case Question.TYPE_ESSAY:
itemBuilder = convertEssay(item);
break;
}
} else {
errors.add(item.getTitle());
log.error("Item without question: " + item);
}
if(itemBuilder != null) {
itemBuilder.build();
AssessmentItem assessmentItem = itemBuilder.getAssessmentItem();
AssessmentItemRef itemRef = new AssessmentItemRef(assessmentSection);
String itemId = IdentifierGenerator.newAsString(itemBuilder.getQuestionType().getPrefix());
itemRef.setIdentifier(Identifier.parseString(itemId));
convertItemBasics(item, itemRef);
File itemFile = new File(unzippedDirRoot, itemId + ".xml");
itemRef.setHref(new URI(itemFile.getName()));
assessmentSection.getSectionParts().add(itemRef);
persistAssessmentObject(itemFile, assessmentItem);
appendResourceAndMetadata(item, itemBuilder, itemFile);
//collect max score
Double maxScore = QtiNodesExtractor.extractMaxScore(assessmentItem);
if(maxScore != null && maxScore.doubleValue() > 0.0d) {
atomicMaxScore.add(maxScore.doubleValue());
}
}
}
}
public boolean convert(QuestionItemImpl convertedItem, Item item, VFSContainer originalContainer) {
if(convertItem(convertedItem, item)) {
copyMaterial(originalContainer);
manifest.appendAssessmentItem(convertedItem.getRootFilename());
manifest.write(new File(unzippedDirRoot, "imsmanifest.xml"));
return true;
}
return false;
}
private void copyMaterial(VFSContainer originalContainer) {
Set<String> materialSet = new HashSet<>(materialPath);
for(String material:materialSet) {
if(StringHelper.containsNonWhitespace(material)
&& !material.startsWith("http://") && !material.startsWith("https://")) {
VFSItem materialItem = originalContainer.resolve(material);
if(materialItem instanceof VFSLeaf) {
try(InputStream in = ((VFSLeaf) materialItem).getInputStream()) {
if(materialMappings.containsKey(material)) {
material = materialMappings.get(material);
}
File dest = new File(unzippedDirRoot, material);
FileUtils.copyToFile(in, dest, "");
} catch(Exception e) {
log.error("Cannot copy: " + material, e);
}
}
}
}
}
/**
*
* @param item
* @return The name of the assesssmentItem file
*/
private boolean convertItem(QuestionItemImpl convertedQuestion, Item item) {
QItemTypeDAO qItemTypeDao = CoreSpringFactory.getImpl(QItemTypeDAO.class);
AssessmentItemBuilder itemBuilder = null;
int questionType = item.getQuestion().getType();
switch (questionType) {
case Question.TYPE_SC:
itemBuilder = convertSingleChoice(item);
convertedQuestion.setType(qItemTypeDao.loadByType(QuestionType.SC.name()));
break;
case Question.TYPE_MC:
itemBuilder = convertMultipleChoice(item);
convertedQuestion.setType(qItemTypeDao.loadByType(QuestionType.MC.name()));
break;
case Question.TYPE_KPRIM:
itemBuilder = convertKPrim(item);
convertedQuestion.setType(qItemTypeDao.loadByType(QuestionType.KPRIM.name()));
break;
case Question.TYPE_FIB:
itemBuilder = convertFIB(item);
convertedQuestion.setType(qItemTypeDao.loadByType(QuestionType.FIB.name()));
break;
case Question.TYPE_ESSAY:
itemBuilder = convertEssay(item);
convertedQuestion.setType(qItemTypeDao.loadByType(QuestionType.ESSAY.name()));
break;
}
if(itemBuilder != null) {
itemBuilder.build();
AssessmentItem assessmentItem = itemBuilder.getAssessmentItem();
String itemId = IdentifierGenerator.newAsString(itemBuilder.getQuestionType().getPrefix());
File itemFile = new File(unzippedDirRoot, itemId + ".xml");
persistAssessmentObject(itemFile, assessmentItem);
appendResourceAndMetadata(item, itemBuilder, itemFile);
convertedQuestion.setRootFilename(itemFile.getName());
return true;
}
return false;
}
private void convertItemBasics(Item item, AssessmentItemRef itemRef) {
if(item.getMaxattempts() > 0) {
ItemSessionControl itemSessionControl = itemRef.getItemSessionControl();
if(itemSessionControl == null) {
itemSessionControl = new ItemSessionControl(itemRef);
itemRef.setItemSessionControl(itemSessionControl);
}
itemSessionControl.setMaxAttempts(item.getMaxattempts());
}
if(item.getDuration() != null && item.getDuration().isSet()) {
TimeLimits timeLimits = itemRef.getTimeLimits();
if(timeLimits == null) {
timeLimits = new TimeLimits(itemRef);
itemRef.setTimeLimits(timeLimits);
}
timeLimits.setMinimum(0.0d);
double max = 0.0d;
if(item.getDuration().getMin() > 0) {
max += item.getDuration().getMin() * 60d;
}
if(item.getDuration().getSec() > 0) {
max += item.getDuration().getSec();
}
timeLimits.setMaximum(max);
}
}
private void appendResourceAndMetadata(Item item, AssessmentItemBuilder itemBuilder, File itemFile) {
manifest.appendAssessmentItem(itemFile.getName());
ManifestMetadataBuilder metadata = manifest.getResourceBuilderByHref(itemFile.getName());
metadata.setTechnicalFormat(ManifestBuilder.ASSESSMENTITEM_MIMETYPE);
metadata.setQtiMetadata(itemBuilder.getInteractionNames());
metadata.setOpenOLATMetadataQuestionType(itemBuilder.getQuestionType().getPrefix());
metadata.setTitle(item.getTitle(), locale.getLanguage());
metadata.setDescription(item.getObjectives(), locale.getLanguage());
}
public boolean persistAssessmentObject(File resourceFile, AssessmentObject assessmentObject) {
try(FileOutputStream out = new FileOutputStream(resourceFile)) {
qtiSerializer.serializeJqtiObject(assessmentObject, out);
return true;
} catch(Exception e) {
log.error("", e);
return false;
}
}
private AssessmentItemBuilder convertSingleChoice(Item item) {
SingleChoiceAssessmentItemBuilder itemBuilder = new SingleChoiceAssessmentItemBuilder("Single choice", "New answer", qtiSerializer);
convertItemBasics(item, itemBuilder);
itemBuilder.clearMapping();
itemBuilder.clearSimpleChoices();
itemBuilder.setScoreEvaluationMode(ScoreEvaluation.allCorrectAnswers);
ChoiceInteraction interaction = itemBuilder.getChoiceInteraction();
Question question = item.getQuestion();
itemBuilder.setShuffle(question.isShuffle());
convertOrientation(question, itemBuilder);
List<Response> responses = question.getResponses();
for(Response response:responses) {
String responseText = response.getContent().renderAsHtmlForEditor();
responseText = blockedHtml(responseText);
SimpleChoice newChoice;
if(StringHelper.isHtml(responseText)) {
newChoice = AssessmentItemFactory
.createSimpleChoice(interaction, "", itemBuilder.getQuestionType().getPrefix());
htmlBuilder.appendHtml(newChoice, responseText);
} else {
newChoice = AssessmentItemFactory
.createSimpleChoice(interaction, responseText, itemBuilder.getQuestionType().getPrefix());
}
itemBuilder.addSimpleChoice(newChoice);
if(response.isCorrect()) {
itemBuilder.setCorrectAnswer(newChoice.getIdentifier());
}
}
double correctScore = question.getSingleCorrectScore();
if(correctScore >= 0.0d) {
itemBuilder.setMinScore(0.0d);
itemBuilder.setMaxScore(correctScore);
}
return itemBuilder;
}
private AssessmentItemBuilder convertMultipleChoice(Item item) {
MultipleChoiceAssessmentItemBuilder itemBuilder = new MultipleChoiceAssessmentItemBuilder("Multiple choice", "New answer", qtiSerializer);
convertItemBasics(item, itemBuilder);
itemBuilder.clearMapping();
itemBuilder.clearSimpleChoices();
ChoiceInteraction interaction = itemBuilder.getChoiceInteraction();
Question question = item.getQuestion();
itemBuilder.setShuffle(question.isShuffle());
convertOrientation(question, itemBuilder);
boolean hasNegative = false;
List<Response> responses = question.getResponses();
for(Response response:responses) {
if(response.getPoints() < 0.0f) {
hasNegative = true;
}
}
boolean singleCorrect = question.isSingleCorrect();
for(Response response:responses) {
String responseText = response.getContent().renderAsHtmlForEditor();
responseText = blockedHtml(responseText);
SimpleChoice newChoice;
if(StringHelper.isHtml(responseText)) {
newChoice = AssessmentItemFactory
.createSimpleChoice(interaction, "", itemBuilder.getQuestionType().getPrefix());
htmlBuilder.appendHtml(newChoice, responseText);
} else {
newChoice = AssessmentItemFactory
.createSimpleChoice(interaction, responseText, itemBuilder.getQuestionType().getPrefix());
}
itemBuilder.addSimpleChoice(newChoice);
double score = response.getPoints();
if(singleCorrect) {
if(response.isCorrect()) {
itemBuilder.addCorrectAnswer(newChoice.getIdentifier());
}
if(score > 0.0f) {
itemBuilder.setMaxScore(score);
}
} else {
if((hasNegative && response.getPoints() >= 0.0f) || (!hasNegative && response.getPoints() > 0.0f)) {
itemBuilder.addCorrectAnswer(newChoice.getIdentifier());
}
itemBuilder.setMapping(newChoice.getIdentifier(), score);
}
}
if(singleCorrect) {
itemBuilder.setScoreEvaluationMode(ScoreEvaluation.allCorrectAnswers);
} else {
itemBuilder.setScoreEvaluationMode(ScoreEvaluation.perAnswer);
if(question instanceof ChoiceQuestion) {
ChoiceQuestion choice = (ChoiceQuestion)question;
itemBuilder.setMinScore(new Double(choice.getMinValue()));
itemBuilder.setMaxScore(new Double(choice.getMaxValue()));
}
}
return itemBuilder;
}
private void convertOrientation(Question question, SimpleChoiceAssessmentItemBuilder itemBuilder) {
if (question instanceof ChoiceQuestion) {
String flowLabel = ((ChoiceQuestion)question).getFlowLabelClass();
if(StringHelper.containsNonWhitespace(flowLabel)) {
if(ChoiceQuestion.BLOCK.equals(flowLabel)) {
itemBuilder.setOrientation(Orientation.HORIZONTAL);
} else if(ChoiceQuestion.LIST.equals(flowLabel)) {
itemBuilder.setOrientation(Orientation.VERTICAL);
}
}
}
}
private AssessmentItemBuilder convertKPrim(Item item) {
KPrimAssessmentItemBuilder itemBuilder = new KPrimAssessmentItemBuilder("Kprim", "New answer", qtiSerializer);
convertItemBasics(item, itemBuilder);
Question question = item.getQuestion();
itemBuilder.setShuffle(question.isShuffle());
List<Response> responses = question.getResponses();
List<SimpleAssociableChoice> choices = itemBuilder.getKprimChoices();
for(int i=0; i<4; i++) {
Response response = responses.get(i);
SimpleAssociableChoice choice = choices.get(i);
String answer = response.getContent().renderAsHtmlForEditor();
answer = blockedHtml(answer);
if(StringHelper.isHtml(answer)) {
htmlBuilder.appendHtml(choice, answer);
} else {
P firstChoiceText = AssessmentItemFactory.getParagraph(choice, answer);
choice.getFlowStatics().clear();
choice.getFlowStatics().add(firstChoiceText);
}
if(response.isCorrect()) {
itemBuilder.setAssociation(choice.getIdentifier(), QTI21Constants.CORRECT_IDENTIFIER);
} else {
itemBuilder.setAssociation(choice.getIdentifier(), QTI21Constants.WRONG_IDENTIFIER);
}
}
double score = question.getMaxValue();
itemBuilder.setMinScore(0.0d);
itemBuilder.setMaxScore(score);
return itemBuilder;
}
private AssessmentItemBuilder convertFIB(Item item) {
FIBAssessmentItemBuilder itemBuilder = new FIBAssessmentItemBuilder("Gap text", EntryType.text, qtiSerializer);
itemBuilder.setQuestion("");
itemBuilder.clearTextEntries();
convertItemBasics(item, itemBuilder);
Question question = item.getQuestion();
boolean singleCorrect = question.isSingleCorrect();
if(singleCorrect) {
itemBuilder.setScoreEvaluationMode(ScoreEvaluation.allCorrectAnswers);
} else {
itemBuilder.setScoreEvaluationMode(ScoreEvaluation.perAnswer);
}
itemBuilder.getMinScoreBuilder().setScore(new Double(question.getMinValue()));
itemBuilder.getMaxScoreBuilder().setScore(new Double(question.getMaxValue()));
List<Response> responses = question.getResponses();
StringBuilder sb = new StringBuilder();
for(Response response:responses) {
if(response instanceof FIBResponse) {
FIBResponse gap = (FIBResponse)response;
if(FIBResponse.TYPE_BLANK.equals(gap.getType())) {
String responseId = itemBuilder.generateResponseIdentifier();
StringBuilder entryString = new StringBuilder();
entryString.append(" <textentryinteraction responseidentifier=\"").append(responseId).append("\"");
TextEntry entry = itemBuilder.createTextEntry(responseId);
entry.setCaseSensitive("Yes".equals(gap.getCaseSensitive()));
if(gap.getMaxLength() > 0) {
entry.setExpectedLength(gap.getMaxLength());
entryString.append(" expectedlength=\"").append(gap.getMaxLength()).append("\"");
} else if(gap.getSize() > 0) {
entry.setExpectedLength(gap.getSize());
entryString.append(" expectedlength=\"").append(gap.getSize()).append("\"");
}
parseAlternatives(gap.getCorrectBlank(), gap.getPoints(), entry);
entryString.append("></textentryinteraction>");
sb.append(entryString);
} else if(FIBResponse.TYPE_CONTENT.equals(gap.getType())) {
Material text = gap.getContent();
String htmltext = text.renderAsHtmlForEditor();
htmltext = blockedHtml(htmltext);
sb.append(htmltext);
}
}
}
String fib = "<div>" + sb.toString() + "</div>";
itemBuilder.setQuestion(fib);
return itemBuilder;
}
private void parseAlternatives(String value, double score, TextEntry textEntry) {
String[] values = value.split(";");
if(values.length > 0) {
textEntry.setSolution(values[0]);
textEntry.setScore(score);
}
if(values.length > 1) {
for(int i=1; i<values.length; i++) {
textEntry.addAlternative(values[i], score);
}
}
}
private AssessmentItemBuilder convertEssay(Item item) {
EssayAssessmentItemBuilder itemBuilder = new EssayAssessmentItemBuilder("Essay", qtiSerializer);
convertItemBasics(item, itemBuilder);
EssayQuestion question = (EssayQuestion)item.getQuestion();
EssayResponse response = question.getEssayResponse();
int cols = response.getColumns();
int rows = response.getRows();
itemBuilder.setExpectedLength(cols * rows);
itemBuilder.setExpectedLines(rows);
double score = question.getMaxValue();
itemBuilder.setMinScore(0.0d);
itemBuilder.setMaxScore(score);
return itemBuilder;
}
private void convertItemBasics(Item item, AssessmentItemBuilder itemBuilder) {
AssessmentItem assessmentItem = itemBuilder.getAssessmentItem();
if(StringHelper.containsNonWhitespace(item.getTitle())) {
assessmentItem.setTitle(item.getTitle());
}
if(StringHelper.containsNonWhitespace(item.getLabel())) {
assessmentItem.setLabel(item.getLabel());
}
Question question = item.getQuestion();
String questionText = question.getQuestion().renderAsHtmlForEditor();
questionText = blockedHtml(questionText);
if(StringHelper.isHtml(questionText)) {
itemBuilder.setQuestion(questionText);
} else {
itemBuilder.setQuestion("<p>" + questionText + "</p>");
}
String hintText = question.getHintText();
if(StringHelper.containsNonWhitespace(hintText)) {
ModalFeedbackBuilder hint = itemBuilder.createHint();
Translator translator = Util.createPackageTranslator(QTIModule.class, locale);
hint.setTitle(translator.translate("render.hint"));
hint.setText(hintText);
}
String solutionText = question.getSolutionText();
if(StringHelper.containsNonWhitespace(solutionText)) {
ModalFeedbackBuilder solution = itemBuilder.createCorrectSolutionFeedback();
solution.setText(solutionText);
}
String feedbackMastery = QTIEditHelper.getFeedbackMasteryText(item);
if(StringHelper.containsNonWhitespace(feedbackMastery)) {
ModalFeedbackBuilder feedback = itemBuilder.createCorrectFeedback();
feedback.setText(feedbackMastery);
}
String feedbackFail = QTIEditHelper.getFeedbackFailText(item);
if(StringHelper.containsNonWhitespace(feedbackFail)) {
ModalFeedbackBuilder feedback = itemBuilder.createIncorrectFeedback();
feedback.setText(feedbackFail);
}
}
private void convertDuration(Duration duration, ControlObject<?> parent) {
if(duration != null && duration.isSet()) {
TimeLimits timeLimits = new TimeLimits(parent);
double timeInSeconds = (60 * duration.getMin()) + duration.getSec();
timeLimits.setMaximum(timeInSeconds);
parent.setTimeLimits(timeLimits);
}
}
/**
* Make sure the HTML content is in block elements. Simple text
* are returned as is.
*
* @param text
* @return
*/
protected final String blockedHtml(String text) {
if(StringHelper.containsNonWhitespace(text)) {
collectMaterial(text);
if(StringHelper.isHtml(text)) {
String trimmedText = text.trim();
trimmedText = trimmedText.replace("<hr />", "<hr></hr>");
try {
Writer out = new StringWriter();
XMLOutputFactory xof = XMLOutputFactory.newInstance();
XMLStreamWriter xtw = xof.createXMLStreamWriter(out);
SAXParser parser = new SAXParser();
QTI12To21HtmlHandler handler = new QTI12To21HtmlHandler(xtw);
parser.setContentHandler(handler);
parser.parse(new InputSource(new StringReader(trimmedText)));
String blockedHtml = out.toString();
text = blockedHtml.replace("<start>", "").replace("</start>", "");
materialMappings.putAll(handler.getMaterialsMapping());
} catch (FactoryConfigurationError | XMLStreamException | SAXException | IOException e) {
log.error("", e);
}
} else {
text = StringEscapeUtils.unescapeHtml(text);
}
}
return text;
}
private void collectMaterial(String content) {
try {
SAXParser parser = new SAXParser();
QTI12HtmlHandler contentHandler = new QTI12HtmlHandler(materialPath);
parser.setContentHandler(contentHandler);
parser.parse(new InputSource(new StringReader(content)));
} catch (Exception e) {
log.error("", e);
}
}
public static boolean isConvertible(OLATResource resource) {
if(TestFileResource.TYPE_NAME.equals(resource.getResourceableTypeName())) {
if(OnyxModule.isOnyxTest(resource)) {
return true;
}
QTIDocument doc = TestFileResource.getQTIDocument(resource);
if(doc == null) {
return false;
}
boolean alien = false;
@SuppressWarnings("unchecked")
List<Item> items = doc.getAssessment().getItems();
for(int i=0; i<items.size(); i++) {
Item item = items.get(i);
alien |= item.isAlient();
}
return !alien;
}
return false;
}
}