diff --git a/src/main/java/org/olat/ims/qti21/model/QTI21QuestionType.java b/src/main/java/org/olat/ims/qti21/model/QTI21QuestionType.java index 2ec4cc1b60b5912e208833607c189b32d92a940c..aa1fdf5bc77a7f9f9f806a31d6f63d9c1aff8070 100644 --- a/src/main/java/org/olat/ims/qti21/model/QTI21QuestionType.java +++ b/src/main/java/org/olat/ims/qti21/model/QTI21QuestionType.java @@ -31,7 +31,7 @@ public enum QTI21QuestionType { sc(true, "sc", QuestionType.SC), mc(true, "mc", QuestionType.MC), kprim(true, "kprim", QuestionType.KPRIM), - fib(false, "fib", QuestionType.FIB), + fib(true, "fib", QuestionType.FIB), essay(true, "essay", QuestionType.ESSAY), unkown(false, null, null); diff --git a/src/main/java/org/olat/ims/qti21/model/xml/interactions/FIBAssessmentItemBuilder.java b/src/main/java/org/olat/ims/qti21/model/xml/interactions/FIBAssessmentItemBuilder.java index 51881ea7c0ec6e5d0a789539df99918d2e2dda68..0101bd64e544afd5cad4d00e4171966f9ecf0cb5 100644 --- a/src/main/java/org/olat/ims/qti21/model/xml/interactions/FIBAssessmentItemBuilder.java +++ b/src/main/java/org/olat/ims/qti21/model/xml/interactions/FIBAssessmentItemBuilder.java @@ -143,6 +143,7 @@ public class FIBAssessmentItemBuilder extends AssessmentItemBuilder { private void extractTextEntrySettingsFromResponseDeclaration() { double mappedScore = 0.0d; + int countAlternatives = 0; for(Map.Entry<String, TextEntry> textEntryEntry:responseIdentifierToTextEntry.entrySet()) { TextEntry textEntry = textEntryEntry.getValue(); @@ -187,10 +188,11 @@ public class FIBAssessmentItemBuilder extends AssessmentItemBuilder { alternative.setAlternative(alt); alternative.setScore(mapEntry.getMappedValue()); alternatives.add(alternative); - mappedScore += mapEntry.getMappedValue(); } else if(alt.equals(solution)) { textEntry.setScore(mapEntry.getMappedValue()); } + countAlternatives++; + mappedScore += mapEntry.getMappedValue(); } caseSensitive &= mapEntry.getCaseSensitive(); @@ -202,7 +204,7 @@ public class FIBAssessmentItemBuilder extends AssessmentItemBuilder { } } - boolean hasMapping = mappedScore > (-1.0 * responseIdentifierToTextEntry.size()); + boolean hasMapping = Math.abs(mappedScore - (-1.0 * countAlternatives)) > 0.0001; scoreEvaluation = hasMapping ? ScoreEvaluation.perAnswer : ScoreEvaluation.allCorrectAnswers; } diff --git a/src/main/java/org/olat/ims/qti21/pool/QTI12To21Converter.java b/src/main/java/org/olat/ims/qti21/pool/QTI12To21Converter.java index ec3c323042d7c4247520336db1bae4311adf5b2d..76545d367a6cb454776491d2a3995dbd46720c70 100644 --- a/src/main/java/org/olat/ims/qti21/pool/QTI12To21Converter.java +++ b/src/main/java/org/olat/ims/qti21/pool/QTI12To21Converter.java @@ -37,7 +37,9 @@ 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; @@ -57,6 +59,7 @@ import org.olat.ims.qti21.model.xml.ModalFeedbackBuilder; import org.olat.ims.qti21.model.xml.interactions.ChoiceAssessmentItemBuilder.ScoreEvaluation; 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.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.SingleChoiceAssessmentItemBuilder; @@ -133,6 +136,14 @@ public class QTI12To21Converter { } AssessmentTestBuilder assessmentTestBuilder = new AssessmentTestBuilder(assessmentTest); + assessmentTestBuilder.setExportScore(true); + + //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(); @@ -148,12 +159,6 @@ public class QTI12To21Converter { } } - //root - List<Section> sections = assessment.getSections(); - for(Section section:sections) { - convert(section, testPart); - } - assessmentTest = assessmentTestBuilder.build(); persistAssessmentObject(testFile, assessmentTest); manifest.write(new File(unzippedDirRoot, "imsmanifest.xml")); @@ -193,10 +198,10 @@ public class QTI12To21Converter { break; case Question.TYPE_KPRIM: itemBuilder = convertKPrim(item); - break;/* + break; case Question.TYPE_FIB: itemBuilder = convertFIB(item); - break;*/ + break; case Question.TYPE_ESSAY: itemBuilder = convertEssay(item); break; @@ -210,6 +215,7 @@ public class QTI12To21Converter { 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); @@ -219,6 +225,36 @@ public class QTI12To21Converter { } } + 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()); @@ -344,12 +380,67 @@ public class QTI12To21Converter { private AssessmentItemBuilder convertFIB(Item item) { FIBAssessmentItemBuilder itemBuilder = new FIBAssessmentItemBuilder(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(); + TextEntry entry = itemBuilder.createTextEntry(responseId); + entry.setCaseSensitive("Yes".equals(gap.getCaseSensitive())); + if(gap.getMaxLength() > 0) { + entry.setExpectedLength(gap.getMaxLength()); + } else if(gap.getSize() > 0) { + entry.setExpectedLength(gap.getSize()); + } + parseAlternatives(gap.getCorrectBlank(), gap.getPoints(), entry); + + String entryString = " <textEntryInteraction responseIdentifier=\"" + responseId + "\"/>"; + sb.append(entryString); + } else if(FIBResponse.TYPE_CONTENT.equals(gap.getType())) { + Material text = gap.getContent(); + String htmltext = text.renderAsHtmlForEditor(); + sb.append(htmltext); + } + } + } + + String fib = sb.toString(); + if(!fib.startsWith("<p") && !fib.startsWith("<div")) { + fib = "<p>" + fib + "</p>"; + } + 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.addAlterantive(values[i], score); + } + } + } + private AssessmentItemBuilder convertEssay(Item item) { EssayAssessmentItemBuilder itemBuilder = new EssayAssessmentItemBuilder(qtiSerializer); convertItemBasics(item, itemBuilder); @@ -376,14 +467,6 @@ public class QTI12To21Converter { if(StringHelper.containsNonWhitespace(item.getLabel())) { assessmentItem.setLabel(item.getLabel()); } - if(StringHelper.containsNonWhitespace(item.getObjectives())) { - //metadata description item.getObjectives() - } - - if(item.getMaxattempts() > 0 - || (item.getDuration() != null && item.getDuration().isSet())) { - //not supported - } Question question = item.getQuestion(); String questionText = question.getQuestion().renderAsHtmlForEditor(); diff --git a/src/main/java/org/olat/ims/qti21/pool/QTI21AssessmentItemFactory.java b/src/main/java/org/olat/ims/qti21/pool/QTI21AssessmentItemFactory.java index 81c5fd64d73f3e843f51b7caf68507934bc71742..570fab3ac8d13bc8b1b019b12660641e1d00d99a 100644 --- a/src/main/java/org/olat/ims/qti21/pool/QTI21AssessmentItemFactory.java +++ b/src/main/java/org/olat/ims/qti21/pool/QTI21AssessmentItemFactory.java @@ -53,7 +53,7 @@ public class QTI21AssessmentItemFactory implements QItemFactory { case sc: return "QTI 2.1 " + trans.translate("new.sc"); case mc: return "QTI 2.1 " + trans.translate("new.mc"); case kprim: return "QTI 2.1 " + trans.translate("new.kprim"); - //case fib: return "QTI 2.1 " + trans.translate("item.type.fib"); + case fib: return "QTI 2.1 " + trans.translate("new.fib"); case essay: return "QTI 2.1 " + trans.translate("new.essay"); default: return type.name(); } diff --git a/src/main/java/org/olat/ims/qti21/pool/QTI21QPoolServiceProvider.java b/src/main/java/org/olat/ims/qti21/pool/QTI21QPoolServiceProvider.java index 3b2536bde028f2ed6580596d80e705b9136d19c1..979f4ab105a099b1a7df5e76f19cad2b70b6e5c8 100644 --- a/src/main/java/org/olat/ims/qti21/pool/QTI21QPoolServiceProvider.java +++ b/src/main/java/org/olat/ims/qti21/pool/QTI21QPoolServiceProvider.java @@ -30,7 +30,6 @@ import java.util.Locale; import java.util.Set; import java.util.zip.ZipOutputStream; -import org.olat.core.commons.persistence.DB; import org.olat.core.gui.UserRequest; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; @@ -54,6 +53,7 @@ import org.olat.ims.qti21.model.xml.AssessmentItemBuilder; import org.olat.ims.qti21.model.xml.AssessmentItemMetadata; import org.olat.ims.qti21.model.xml.ManifestBuilder; 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.KPrimAssessmentItemBuilder; import org.olat.ims.qti21.model.xml.interactions.MultipleChoiceAssessmentItemBuilder; import org.olat.ims.qti21.model.xml.interactions.SingleChoiceAssessmentItemBuilder; @@ -95,8 +95,6 @@ public class QTI21QPoolServiceProvider implements QPoolSPI { public static final String QTI_12_OO_TEST = "OpenOLAT Test"; - @Autowired - private DB dbInstance; @Autowired private QTI21Service qtiService; @@ -240,7 +238,7 @@ public class QTI21QPoolServiceProvider implements QPoolSPI { case sc: itemBuilder = new SingleChoiceAssessmentItemBuilder(qtiService.qtiSerializer()); break; case mc: itemBuilder = new MultipleChoiceAssessmentItemBuilder(qtiService.qtiSerializer()); break; case kprim: itemBuilder = new KPrimAssessmentItemBuilder(qtiService.qtiSerializer()); break; - //case fib: item = QTIEditHelper.createFIBItem(trans); break; + case fib: itemBuilder = new FIBAssessmentItemBuilder(qtiService.qtiSerializer()); break; case essay: itemBuilder = new EssayAssessmentItemBuilder(qtiService.qtiSerializer()); break; default: return null; }