diff --git a/src/main/java/org/olat/ims/qti/QTI12EditorController.java b/src/main/java/org/olat/ims/qti/QTI12EditorController.java index 5d79dc64f4a08427fc2ae8688034b581c14e4d90..3bada7e691de4c5d11e24c09a29e048b4e1ba9e0 100644 --- a/src/main/java/org/olat/ims/qti/QTI12EditorController.java +++ b/src/main/java/org/olat/ims/qti/QTI12EditorController.java @@ -19,7 +19,6 @@ */ package org.olat.ims.qti; -import org.olat.core.CoreSpringFactory; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.tabbedpane.TabbedPane; @@ -42,6 +41,7 @@ import org.olat.modules.qpool.QPoolItemEditorController; import org.olat.modules.qpool.QPoolService; import org.olat.modules.qpool.QuestionItem; import org.olat.modules.qpool.ui.events.QItemChangeEvent; +import org.springframework.beans.factory.annotation.Autowired; /** * * Initial date: 21.02.2013<br> @@ -54,12 +54,13 @@ public class QTI12EditorController extends BasicController implements QPoolItemE private final VelocityContainer mainVC; private ItemNodeTabbedFormController editorsCtrl; - private final QPoolService qpoolService; private final QuestionItem qitem; + + @Autowired + private QPoolService qpoolService; public QTI12EditorController(UserRequest ureq, WindowControl wControl, QuestionItem qitem) { super(ureq, wControl); - qpoolService = CoreSpringFactory.getImpl(QPoolService.class); this.qitem = qitem; mainVC = createVelocityContainer("qti_preview"); diff --git a/src/main/java/org/olat/ims/qti/editor/QTIEditorMainController.java b/src/main/java/org/olat/ims/qti/editor/QTIEditorMainController.java index 8c306d36371f2f4ab416610304da312d22a33b0d..cdace79f7bc1a3d0b547a7aab859a5a64a4ef708 100644 --- a/src/main/java/org/olat/ims/qti/editor/QTIEditorMainController.java +++ b/src/main/java/org/olat/ims/qti/editor/QTIEditorMainController.java @@ -98,6 +98,7 @@ import org.olat.course.nodes.CourseNode; import org.olat.course.nodes.iq.IQEditController; import org.olat.course.tree.TreePosition; import org.olat.fileresource.types.FileResource; +import org.olat.fileresource.types.ImsQTI21Resource; import org.olat.ims.qti.QTIChangeLogMessage; import org.olat.ims.qti.QTIConstants; import org.olat.ims.qti.QTIResultManager; @@ -133,6 +134,9 @@ import org.olat.modules.qpool.ui.events.QItemViewEvent; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryManager; import org.olat.repository.RepositoryService; +import org.olat.repository.handlers.RepositoryHandler; +import org.olat.repository.handlers.RepositoryHandlerFactory; +import org.olat.repository.ui.author.CreateRepositoryEntryController; import org.olat.resource.references.Reference; import org.olat.user.UserManager; import org.springframework.beans.factory.annotation.Autowired; @@ -176,6 +180,7 @@ public class QTIEditorMainController extends MainLayoutBasicController implement private static final String CMD_TOOLS_EXPORT_QPOOL = "cmd.export.qpool"; private static final String CMD_TOOLS_EXPORT_DOCX = "cmd.export.docx"; private static final String CMD_TOOLS_IMPORT_TABLE = "cmd.import.xls"; + private static final String CMD_TOOLS_CONVERT_TO_QTI21 = "cmd.convert.qti.21"; private static final String CMD_EXIT_SAVE = "exit.save"; private static final String CMD_EXIT_DISCARD = "exit.discard"; @@ -229,7 +234,7 @@ public class QTIEditorMainController extends MainLayoutBasicController implement private TooledStackedPanel stackedPanel; private LayoutMain3ColsController columnLayoutCtr; - private Link previewLink, exportPoolLink, exportDocLink, importTableLink, closeLink; + private Link previewLink, exportPoolLink, convertQTI21Link, exportDocLink, importTableLink, closeLink; private Link addPoolLink, addSectionLink, addSCLink, addMCLink, addFIBLink, addKPrimLink, addEssayLink; private Link deleteLink, moveLink, copyLink; @@ -255,6 +260,7 @@ public class QTIEditorMainController extends MainLayoutBasicController implement private Link notEditableButton; private Set<String> deletableMediaFiles; private StepsMainRunController importTableWizard; + private CreateRepositoryEntryController createTestController; private InsertNodeController moveCtrl, copyCtrl, insertCtrl; @Autowired @@ -267,6 +273,8 @@ public class QTIEditorMainController extends MainLayoutBasicController implement private RepositoryService repositoryService; @Autowired private QTIQPoolServiceProvider qtiQpoolServiceProvider; + @Autowired + private RepositoryHandlerFactory repositoryHandlerFactory; public QTIEditorMainController(UserRequest ureq, WindowControl wControl, RepositoryEntry qtiEntry, List<Reference> referencees, FileResource fileResource) { super(ureq, wControl); @@ -621,6 +629,8 @@ public class QTIEditorMainController extends MainLayoutBasicController implement doExportQItem(); } else if (exportDocLink == source) { doExportDocx(ureq); + } else if (convertQTI21Link == source) { + doConvertToQTI21(ureq); } else if (importTableLink == source) { doImportTable(ureq); } else if (addSectionLink == source) { @@ -1020,6 +1030,20 @@ public class QTIEditorMainController extends MainLayoutBasicController implement cmc.activate(); listenTo(cmc); } + + private void doConvertToQTI21(UserRequest ureq) { + removeAsListenerAndDispose(cmc); + removeAsListenerAndDispose(selectQItemCtrl); + + RepositoryHandler handler = repositoryHandlerFactory.getRepositoryHandler(ImsQTI21Resource.TYPE_NAME); + createTestController = new CreateRepositoryEntryController(ureq, getWindowControl(), handler); + createTestController.setCreateObject(qtiPackage); + listenTo(createTestController); + + cmc = new CloseableModalController(getWindowControl(), translate("close"), selectQItemCtrl.getInitialComponent(), true, translate("title.add") ); + cmc.activate(); + listenTo(cmc); + } private void doExportDocx(UserRequest ureq) { AssessmentNode rootNode = (AssessmentNode)menuTreeModel.getRootNode(); @@ -1124,6 +1148,9 @@ public class QTIEditorMainController extends MainLayoutBasicController implement exportDocLink = LinkFactory.createToolLink(CMD_TOOLS_EXPORT_DOCX, translate("tools.export.docx"), this, "o_mi_docx_export"); exportDocLink.setIconLeftCSS("o_icon o_icon_download"); exportTools.addComponent(exportDocLink); + convertQTI21Link = LinkFactory.createToolLink(CMD_TOOLS_CONVERT_TO_QTI21, translate("tools.convert.qti21"), this, "o_FileResource-IMSQTI21_icon"); + convertQTI21Link.setIconLeftCSS("o_icon o_FileResource-IMSQTI21_icon"); + exportTools.addComponent(convertQTI21Link); //add Dropdown addItemTools = new Dropdown("editTools", "tools.add.header", false, getTranslator()); diff --git a/src/main/java/org/olat/ims/qti/editor/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/ims/qti/editor/_i18n/LocalStrings_de.properties index d80b89268cc796ab58f7ee5e8aab4bf7392284d2..68a9a00b291ee0ce68344b82e5ea2b1eca65e35e 100644 --- a/src/main/java/org/olat/ims/qti/editor/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/ims/qti/editor/_i18n/LocalStrings_de.properties @@ -329,6 +329,7 @@ tools.change.header=Elemente \u00E4ndern tools.change.copy=Kopieren tools.change.delete=L\u00F6schen tools.change.move=Verschieben +tools.convert.qti21=Zu QTI 2.1 umwandeln tools.matedit.back=Zur\u00FCck tools.matedit.header=Editorwerkzeuge tools.import.qpool=Fragen aus Pool importieren diff --git a/src/main/java/org/olat/ims/qti/editor/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/ims/qti/editor/_i18n/LocalStrings_en.properties index 1ec08869cf58225a9cdde6311b4c112f20e0a5fd..1241eb7d6fb2d0864b40c92f4707f82d21d845e9 100644 --- a/src/main/java/org/olat/ims/qti/editor/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/ims/qti/editor/_i18n/LocalStrings_en.properties @@ -214,6 +214,7 @@ tab.survey=Questionnaire title.add=Add element title.copy=Copy element title.move=Move element +tools.convert.qti21=Convert to QTI 2.1 tools.import.qpool=Import questions from pool tools.import.table=Import questions from Excel tools.export.header=Export diff --git a/src/main/java/org/olat/ims/qti21/manager/AssessmentTestPreWarm.java b/src/main/java/org/olat/ims/qti21/manager/AssessmentTestPreWarm.java index b882a301da4b3c26a3fc473a5899af31c55591c5..573113806987f38079da51d6612a5b725ac5ddce 100644 --- a/src/main/java/org/olat/ims/qti21/manager/AssessmentTestPreWarm.java +++ b/src/main/java/org/olat/ims/qti21/manager/AssessmentTestPreWarm.java @@ -1,3 +1,22 @@ +/** + * <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.manager; import java.io.File; diff --git a/src/main/java/org/olat/ims/qti21/model/xml/AssessmentTestBuilder.java b/src/main/java/org/olat/ims/qti21/model/xml/AssessmentTestBuilder.java index 96fc883d5867c20b021c61ae2352118b4e3603b4..4bb950b446a99ad681fd07cd1a13e77506ac1686 100644 --- a/src/main/java/org/olat/ims/qti21/model/xml/AssessmentTestBuilder.java +++ b/src/main/java/org/olat/ims/qti21/model/xml/AssessmentTestBuilder.java @@ -36,6 +36,7 @@ import uk.ac.ed.ph.jqtiplus.node.test.TestFeedback; import uk.ac.ed.ph.jqtiplus.node.test.outcome.processing.OutcomeCondition; import uk.ac.ed.ph.jqtiplus.node.test.outcome.processing.OutcomeConditionChild; import uk.ac.ed.ph.jqtiplus.node.test.outcome.processing.OutcomeIf; +import uk.ac.ed.ph.jqtiplus.node.test.outcome.processing.OutcomeProcessing; import uk.ac.ed.ph.jqtiplus.node.test.outcome.processing.OutcomeRule; import uk.ac.ed.ph.jqtiplus.node.test.outcome.processing.SetOutcomeValue; import uk.ac.ed.ph.jqtiplus.types.Identifier; @@ -74,25 +75,27 @@ public class AssessmentTestBuilder { } private void extractRules() { - List<OutcomeRule> outcomeRules = assessmentTest.getOutcomeProcessing().getOutcomeRules(); - for(OutcomeRule outcomeRule:outcomeRules) { - // export test score - if(outcomeRule instanceof SetOutcomeValue) { - SetOutcomeValue setOutcomeValue = (SetOutcomeValue)outcomeRule; - if(QTI21Constants.SCORE_IDENTIFIER.equals(setOutcomeValue.getIdentifier())) { - testScore = true; - testScoreRule = outcomeRule; + if(assessmentTest.getOutcomeProcessing() != null) { + List<OutcomeRule> outcomeRules = assessmentTest.getOutcomeProcessing().getOutcomeRules(); + for(OutcomeRule outcomeRule:outcomeRules) { + // export test score + if(outcomeRule instanceof SetOutcomeValue) { + SetOutcomeValue setOutcomeValue = (SetOutcomeValue)outcomeRule; + if(QTI21Constants.SCORE_IDENTIFIER.equals(setOutcomeValue.getIdentifier())) { + testScore = true; + testScoreRule = outcomeRule; + } } - } - - // pass rule - if(outcomeRule instanceof OutcomeCondition) { - OutcomeCondition outcomeCondition = (OutcomeCondition)outcomeRule; - boolean findIf = findSetOutcomeValue(outcomeCondition.getOutcomeIf(), QTI21Constants.PASS_IDENTIFIER); - boolean findElse = findSetOutcomeValue(outcomeCondition.getOutcomeElse(), QTI21Constants.PASS_IDENTIFIER); - if(findIf && findElse) { - cutValue = extractCutValue(outcomeCondition.getOutcomeIf()); - cutValueRule = outcomeCondition; + + // pass rule + if(outcomeRule instanceof OutcomeCondition) { + OutcomeCondition outcomeCondition = (OutcomeCondition)outcomeRule; + boolean findIf = findSetOutcomeValue(outcomeCondition.getOutcomeIf(), QTI21Constants.PASS_IDENTIFIER); + boolean findElse = findSetOutcomeValue(outcomeCondition.getOutcomeElse(), QTI21Constants.PASS_IDENTIFIER); + if(findIf && findElse) { + cutValue = extractCutValue(outcomeCondition.getOutcomeIf()); + cutValueRule = outcomeCondition; + } } } } @@ -144,6 +147,10 @@ public class AssessmentTestBuilder { } } + public AssessmentTest getAssessmentTest() { + return assessmentTest; + } + public boolean isExportScore() { return testScore; } @@ -178,16 +185,27 @@ public class AssessmentTestBuilder { return failedFeedback; } - - public void build() { + public AssessmentTest build() { Double maxScore = 1.0d; + if(assessmentTest.getOutcomeProcessing() == null) { + assessmentTest.setOutcomeProcessing(new OutcomeProcessing(assessmentTest)); + } + buildScore(maxScore); buildTestScore(); buildCutValue(); buildFeedback(); + + //clean up + if(assessmentTest.getOutcomeProcessing().getOutcomeRules().isEmpty()) { + assessmentTest.setOutcomeProcessing(null); + } + return assessmentTest; } + + private void buildScore(Double maxScore) { OutcomeDeclaration scoreDeclaration = assessmentTest.getOutcomeDeclaration(QTI21Constants.SCORE_IDENTIFIER); if(scoreDeclaration == null) { diff --git a/src/main/java/org/olat/ims/qti21/model/xml/AssessmentTestFactory.java b/src/main/java/org/olat/ims/qti21/model/xml/AssessmentTestFactory.java index d0401c57ca3a0c7c877ea323a78705fa10b8569e..9ea0c12ea138b598430510c8251e38adfcf0d052 100644 --- a/src/main/java/org/olat/ims/qti21/model/xml/AssessmentTestFactory.java +++ b/src/main/java/org/olat/ims/qti21/model/xml/AssessmentTestFactory.java @@ -98,7 +98,7 @@ public class AssessmentTestFactory { //test part TestPart part = createTestPart(assessmentTest); - createAssessmentSection(part); + appendAssessmentSection(part); //outcome processing OutcomeProcessing outcomeProcessing = new OutcomeProcessing(assessmentTest); @@ -278,15 +278,21 @@ public class AssessmentTestFactory { return part; } - public static AssessmentSection createAssessmentSection(TestPart part) { - return createAssessmentSectionInternal(part); + /** + * create an assessmentSection with an empty rubricBlock for candidate, + * not shuffled but visible and fixed. + * @param part + * @return + */ + public static AssessmentSection appendAssessmentSection(TestPart part) { + return appendAssessmentSectionInternal(part); } - public static AssessmentSection createAssessmentSection(AssessmentSection part) { - return createAssessmentSectionInternal(part); + public static AssessmentSection appendAssessmentSection(AssessmentSection part) { + return appendAssessmentSectionInternal(part); } - private final static AssessmentSection createAssessmentSectionInternal(AbstractPart part) { + private final static AssessmentSection appendAssessmentSectionInternal(AbstractPart part) { // section AssessmentSection section = new AssessmentSection(part); section.setFixed(Boolean.TRUE); diff --git a/src/main/java/org/olat/ims/qti21/model/xml/interactions/ChoiceAssessmentItemBuilder.java b/src/main/java/org/olat/ims/qti21/model/xml/interactions/ChoiceAssessmentItemBuilder.java index 10a334422b2716c8d43388bbb1ab0df3681fb197..8de84981e731a50a7cbeca734d79163408b0c6aa 100644 --- a/src/main/java/org/olat/ims/qti21/model/xml/interactions/ChoiceAssessmentItemBuilder.java +++ b/src/main/java/org/olat/ims/qti21/model/xml/interactions/ChoiceAssessmentItemBuilder.java @@ -115,7 +115,7 @@ public abstract class ChoiceAssessmentItemBuilder extends AssessmentItemBuilder responseIdentifier = choiceInteraction.getResponseIdentifier(); shuffle = choiceInteraction.getShuffle(); break; - } else { + } else if(block != null) { qtiSerializer.serializeJqtiObject(block, new StreamResult(sb)); } } diff --git a/src/main/java/org/olat/ims/qti21/model/xml/interactions/MultipleChoiceAssessmentItemBuilder.java b/src/main/java/org/olat/ims/qti21/model/xml/interactions/MultipleChoiceAssessmentItemBuilder.java index 0b55b9d192d3c69b8b012d50adb53859095578ff..1f418dfca244b55a41b60456216d0e793360fb51 100644 --- a/src/main/java/org/olat/ims/qti21/model/xml/interactions/MultipleChoiceAssessmentItemBuilder.java +++ b/src/main/java/org/olat/ims/qti21/model/xml/interactions/MultipleChoiceAssessmentItemBuilder.java @@ -227,7 +227,8 @@ public class MultipleChoiceAssessmentItemBuilder extends ChoiceAssessmentItemBui </responseElse> </responseCondition> */ - + //simple as build with / without feedback + ensureFeedbackBasicOutcomeDeclaration(); ResponseIf responseIf = new ResponseIf(rule); rule.setResponseIf(responseIf); @@ -269,7 +270,7 @@ public class MultipleChoiceAssessmentItemBuilder extends ChoiceAssessmentItemBui } {//outcome score - SetOutcomeValue scoreOutcomeValue = new SetOutcomeValue(responseIf); + SetOutcomeValue scoreOutcomeValue = new SetOutcomeValue(responseElseIf); scoreOutcomeValue.setIdentifier(QTI21Constants.SCORE_IDENTIFIER); responseElseIf.getResponseRules().add(scoreOutcomeValue); @@ -286,9 +287,9 @@ public class MultipleChoiceAssessmentItemBuilder extends ChoiceAssessmentItemBui } {//outcome feedback - SetOutcomeValue correctOutcomeValue = new SetOutcomeValue(responseIf); + SetOutcomeValue correctOutcomeValue = new SetOutcomeValue(responseElseIf); correctOutcomeValue.setIdentifier(QTI21Constants.FEEDBACKBASIC_IDENTIFIER); - responseIf.getResponseRules().add(correctOutcomeValue); + responseElseIf.getResponseRules().add(correctOutcomeValue); BaseValue correctValue = new BaseValue(correctOutcomeValue); correctValue.setBaseTypeAttrValue(BaseType.IDENTIFIER); @@ -300,9 +301,9 @@ public class MultipleChoiceAssessmentItemBuilder extends ChoiceAssessmentItemBui rule.setResponseElse(responseElse); {// outcome feedback - SetOutcomeValue incorrectOutcomeValue = new SetOutcomeValue(responseIf); + SetOutcomeValue incorrectOutcomeValue = new SetOutcomeValue(responseElse); incorrectOutcomeValue.setIdentifier(QTI21Constants.FEEDBACKBASIC_IDENTIFIER); - responseIf.getResponseRules().add(incorrectOutcomeValue); + responseElse.getResponseRules().add(incorrectOutcomeValue); BaseValue incorrectValue = new BaseValue(incorrectOutcomeValue); incorrectValue.setBaseTypeAttrValue(BaseType.IDENTIFIER); diff --git a/src/main/java/org/olat/ims/qti21/pool/QTI12To21Converter.java b/src/main/java/org/olat/ims/qti21/pool/QTI12To21Converter.java new file mode 100644 index 0000000000000000000000000000000000000000..13628b3c7c1a85e46b4d2884dac70904dd7e9408 --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/pool/QTI12To21Converter.java @@ -0,0 +1,362 @@ +/** + * <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.net.URI; +import java.net.URISyntaxException; +import java.util.List; + +import org.olat.core.helpers.Settings; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.olat.core.util.StringHelper; +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.Control; +import org.olat.ims.qti.editor.beecom.objects.Duration; +import org.olat.ims.qti.editor.beecom.objects.Item; +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.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.ManifestPackage; +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.KPrimAssessmentItemBuilder; +import org.olat.ims.qti21.model.xml.interactions.MultipleChoiceAssessmentItemBuilder; +import org.olat.ims.qti21.model.xml.interactions.SingleChoiceAssessmentItemBuilder; +import org.olat.imscp.xml.manifest.ManifestType; + +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.item.AssessmentItem; +import uk.ac.ed.ph.jqtiplus.node.item.interaction.ChoiceInteraction; +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; + +/** + * + * 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 File unzippedDirRoot; + private final QtiSerializer qtiSerializer = new QtiSerializer(null); + private final AssessmentHtmlBuilder htmlBuilder = new AssessmentHtmlBuilder(qtiSerializer); + + private final ManifestType manifest; + + public QTI12To21Converter(File unzippedDirRoot) { + this.unzippedDirRoot = unzippedDirRoot; + manifest = ManifestPackage.createEmptyManifest(); + } + + public AssessmentTest convert(QTIEditorPackage qtiEditorPackage) + throws URISyntaxException { + return convert(qtiEditorPackage.getQTIDocument()); + } + + public AssessmentTest convert(QTIDocument doc) + throws URISyntaxException { + Assessment assessment = doc.getAssessment(); + + AssessmentTest assessmentTest = new AssessmentTest(); + String assessmentTestIdentifier = IdentifierGenerator.newAssessmentTestFilename(); + File testFile = new File(unzippedDirRoot, assessmentTestIdentifier + ".xml"); + ManifestPackage.appendAssessmentTest(testFile.getName(), manifest); + + 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); + //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); + } + } + } + + //root + List<Section> sections = assessment.getSections(); + for(Section section:sections) { + convert(section, testPart); + } + + assessmentTest = assessmentTestBuilder.build(); + persistAssessmentObject(testFile, assessmentTest); + ManifestPackage.write(manifest, new File(unzippedDirRoot, "imsmanifest.xml")); + return assessmentTest; + } + + private void convert(Section section, TestPart testPart) + throws URISyntaxException { + AssessmentSection assessmentSection = AssessmentTestFactory.appendAssessmentSection(testPart); + assessmentSection.setTitle(section.getTitle()); + convertDuration(section.getDuration(), assessmentSection); + + RubricBlock rubricBlock = assessmentSection.getRubricBlocks().get(0); + rubricBlock.getBlocks().clear(); + htmlBuilder.appendHtml(rubricBlock, section.getObjectives()); + + 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; + 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; + */ + } + + 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)); + File itemFile = new File(unzippedDirRoot, itemId + ".xml"); + itemRef.setHref(new URI(itemFile.getName())); + assessmentSection.getSectionParts().add(itemRef); + persistAssessmentObject(itemFile, assessmentItem); + ManifestPackage.appendAssessmentItem(itemFile.getName(), manifest); + } + } + } + + 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(qtiSerializer); + convertItemBasics(item, itemBuilder); + itemBuilder.clearMapping(); + itemBuilder.clearSimpleChoices(); + itemBuilder.setScoreEvaluationMode(ScoreEvaluation.allCorrectAnswers); + + ChoiceInteraction interaction = itemBuilder.getChoiceInteraction(); + + Question question = item.getQuestion(); + itemBuilder.setShuffle(question.isShuffle()); + + List<Response> responses = question.getResponses(); + for(Response response:responses) { + String responseText = response.getContent().renderAsHtmlForEditor(); + SimpleChoice 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(qtiSerializer); + convertItemBasics(item, itemBuilder); + itemBuilder.clearMapping(); + itemBuilder.clearSimpleChoices(); + + ChoiceInteraction interaction = itemBuilder.getChoiceInteraction(); + + Question question = item.getQuestion(); + itemBuilder.setShuffle(question.isShuffle()); + + boolean singleCorrect = question.isSingleCorrect(); + List<Response> responses = question.getResponses(); + for(Response response:responses) { + String responseText = response.getContent().renderAsHtmlForEditor(); + SimpleChoice newChoice = AssessmentItemFactory + .createSimpleChoice(interaction, responseText, itemBuilder.getQuestionType().getPrefix()); + itemBuilder.addSimpleChoice(newChoice); + if(response.isCorrect()) { + itemBuilder.addCorrectAnswer(newChoice.getIdentifier()); + } + double score = response.getPoints(); + if(singleCorrect) { + if(score > 0.0f) { + itemBuilder.setMaxScore(score); + } + } else { + itemBuilder.setMapping(newChoice.getIdentifier(), score); + } + } + + if(singleCorrect) { + itemBuilder.setScoreEvaluationMode(ScoreEvaluation.allCorrectAnswers); + } else { + itemBuilder.setScoreEvaluationMode(ScoreEvaluation.perAnswer); + } + + return itemBuilder; + } + + private AssessmentItemBuilder convertKPrim(Item item) { + KPrimAssessmentItemBuilder itemBuilder = new KPrimAssessmentItemBuilder(qtiSerializer); + convertItemBasics(item, itemBuilder); + + return itemBuilder; + } + + private AssessmentItemBuilder convertFIB(Item item) { + FIBAssessmentItemBuilder itemBuilder = new FIBAssessmentItemBuilder(qtiSerializer); + convertItemBasics(item, itemBuilder); + + + return itemBuilder; + } + + private AssessmentItemBuilder convertEssay(Item item) { + EssayAssessmentItemBuilder itemBuilder = new EssayAssessmentItemBuilder(qtiSerializer); + convertItemBasics(item, itemBuilder); + + 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()); + } + 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(); + itemBuilder.setQuestion(questionText); + + 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); + } + } +} 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 11c997b1a1d2b1aeb52f958c6ad5e47613aefa8a..7d217b22853ed96c0b34279f1ed6f31ab356ff23 100644 --- a/src/main/java/org/olat/ims/qti21/pool/QTI21QPoolServiceProvider.java +++ b/src/main/java/org/olat/ims/qti21/pool/QTI21QPoolServiceProvider.java @@ -44,6 +44,7 @@ import org.olat.core.util.vfs.VFSItem; import org.olat.core.util.vfs.VFSLeaf; import org.olat.core.util.vfs.VFSManager; import org.olat.fileresource.types.ImsQTI21Resource; +import org.olat.ims.qti.editor.QTIEditorPackage; import org.olat.ims.qti21.QTI21Constants; import org.olat.ims.qti21.QTI21Service; import org.olat.ims.qti21.model.QTI21QuestionType; @@ -72,6 +73,7 @@ import org.olat.modules.qpool.manager.QuestionItemDAO; import org.olat.modules.qpool.manager.TaxonomyLevelDAO; import org.olat.modules.qpool.model.DefaultExportFormat; import org.olat.modules.qpool.model.QuestionItemImpl; +import org.olat.repository.RepositoryEntry; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.xml.sax.InputSource; @@ -303,6 +305,16 @@ public class QTI21QPoolServiceProvider implements QPoolSPI { processor.assembleTest(fullItems, exportDir); } + /** + * Convert from QTI 1.2 to 2.1 + * + * @param qtiEditorPackage + */ + public void convertFromEditorPackage(RepositoryEntry newEntry, QTIEditorPackage qtiEditorPackage) { + qtiEditorPackage.getQTIDocument(); + + } + private List<Long> toKeys(List<? extends QuestionItemShort> items) { List<Long> keys = new ArrayList<Long>(items.size()); for(QuestionItemShort item:items) { diff --git a/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandler.java b/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandler.java index 45b8fea64d01c34fbd9c38ec629d8d26374b9c67..bbb68b84c6fb5579452179124399c43ef0bff78a 100644 --- a/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandler.java +++ b/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandler.java @@ -42,6 +42,7 @@ import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamWriter; import org.olat.core.CoreSpringFactory; +import org.olat.core.commons.persistence.DB; import org.olat.core.commons.persistence.DBFactory; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.stack.TooledStackedPanel; @@ -62,6 +63,7 @@ import org.olat.fileresource.ZippedDirectoryMediaResource; import org.olat.fileresource.types.FileResource; import org.olat.fileresource.types.ImsQTI21Resource; import org.olat.fileresource.types.ResourceEvaluation; +import org.olat.ims.qti.editor.QTIEditorPackage; import org.olat.ims.qti21.QTI21Service; import org.olat.ims.qti21.model.IdentifierGenerator; import org.olat.ims.qti21.model.QTI21QuestionType; @@ -85,6 +87,7 @@ import org.olat.repository.model.RepositoryEntrySecurity; import org.olat.repository.ui.RepositoryEntryRuntimeController.RuntimeControllerCreator; import org.olat.resource.OLATResource; import org.olat.resource.OLATResourceManager; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem; @@ -102,6 +105,13 @@ import uk.ac.ed.ph.jqtiplus.serialization.QtiSerializer; public class QTI21AssessmentTestHandler extends FileHandler { private static final OLog log = Tracing.createLoggerFor(QTI21AssessmentTestHandler.class); + + @Autowired + private DB dbInstance; + @Autowired + private RepositoryService repositoryService; + @Autowired + private QTI21QPoolServiceProvider qpoolServiceProvider; @Override public String getSupportedType() { @@ -122,10 +132,9 @@ public class QTI21AssessmentTestHandler extends FileHandler { public RepositoryEntry createResource(Identity initialAuthor, String displayname, String description, Object createObject, Locale locale) { ImsQTI21Resource ores = new ImsQTI21Resource(); - RepositoryService repositoryService = CoreSpringFactory.getImpl(RepositoryService.class); OLATResource resource = OLATResourceManager.getInstance().findOrPersistResourceable(ores); RepositoryEntry re = repositoryService.create(initialAuthor, null, "", displayname, description, resource, RepositoryEntry.ACC_OWNERS); - DBFactory.getInstance().commit(); + dbInstance.commit(); File repositoryDir = new File(FileResourceManager.getInstance().getFileResourceRoot(re.getOlatResource()), FileResourceManager.ZIPDIR); if(!repositoryDir.exists()) { @@ -133,8 +142,10 @@ public class QTI21AssessmentTestHandler extends FileHandler { } if(createObject instanceof QItemList) { QItemList itemToImport = (QItemList)createObject; - QTI21QPoolServiceProvider provider = CoreSpringFactory.getImpl(QTI21QPoolServiceProvider.class); - provider.exportToEditorPackage(repositoryDir, itemToImport.getItems()); + qpoolServiceProvider.exportToEditorPackage(repositoryDir, itemToImport.getItems()); + } else if(createObject instanceof QTIEditorPackage) { + QTIEditorPackage testToConvert = (QTIEditorPackage)createObject; + qpoolServiceProvider.convertFromEditorPackage(re, testToConvert); } else { createMinimalAssessmentTest(displayname, repositoryDir); } diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentTestComposerController.java b/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentTestComposerController.java index ccba8473261d949a0c53b913f148bdec23dc1b34..0688483485fd1c580f0c8eaf3026b8b7803e8430 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentTestComposerController.java +++ b/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentTestComposerController.java @@ -24,7 +24,6 @@ import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.List; -import java.util.UUID; import org.olat.core.commons.fullWebApp.LayoutMain3ColsController; import org.olat.core.gui.UserRequest; @@ -54,6 +53,7 @@ import org.olat.core.util.Util; import org.olat.fileresource.FileResourceManager; import org.olat.ims.qti21.QTI21Constants; import org.olat.ims.qti21.QTI21Service; +import org.olat.ims.qti21.model.IdentifierGenerator; import org.olat.ims.qti21.model.xml.AssessmentItemBuilder; import org.olat.ims.qti21.model.xml.AssessmentTestFactory; import org.olat.ims.qti21.model.xml.ManifestPackage; @@ -451,9 +451,9 @@ public class AssessmentTestComposerController extends MainLayoutBasicController AssessmentSection newSection; if(parentPart instanceof TestPart) { - newSection = AssessmentTestFactory.createAssessmentSection((TestPart)parentPart); + newSection = AssessmentTestFactory.appendAssessmentSection((TestPart)parentPart); } else if(parentPart instanceof AssessmentSection) { - newSection = AssessmentTestFactory.createAssessmentSection((AssessmentSection)parentPart); + newSection = AssessmentTestFactory.appendAssessmentSection((AssessmentSection)parentPart); } else { showWarning("error.cannot.create.section"); return; @@ -487,7 +487,7 @@ public class AssessmentTestComposerController extends MainLayoutBasicController AssessmentSection section = (AssessmentSection)sectionNode.getUserObject(); AssessmentItemRef itemRef = new AssessmentItemRef(section); - String itemId = "sc" + UUID.randomUUID(); + String itemId = IdentifierGenerator.newAsString(itemBuilder.getQuestionType().getPrefix()); itemRef.setIdentifier(Identifier.parseString(itemId)); File itemFile = new File(unzippedDirRoot, itemId + ".xml"); itemRef.setHref(new URI(itemFile.getName())); diff --git a/src/test/java/org/olat/ims/qti21/pool/QTI12To21ConverterTest.java b/src/test/java/org/olat/ims/qti21/pool/QTI12To21ConverterTest.java new file mode 100644 index 0000000000000000000000000000000000000000..bc7a84788060d3e5fafe7c384a9df9d470e26f1c --- /dev/null +++ b/src/test/java/org/olat/ims/qti21/pool/QTI12To21ConverterTest.java @@ -0,0 +1,69 @@ +/** + * <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.InputStream; +import java.net.URISyntaxException; + +import org.dom4j.Document; +import org.junit.Test; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.olat.core.util.xml.XMLParser; +import org.olat.ims.qti.editor.beecom.objects.QTIDocument; +import org.olat.ims.qti.editor.beecom.parser.ParserManager; +import org.olat.ims.resources.IMSEntityResolver; + +/** + * + * Initial date: 19.02.2016<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class QTI12To21ConverterTest { + + private static final OLog log = Tracing.createLoggerFor(QTI12To21ConverterTest.class); + + @Test + public void convert() throws URISyntaxException { + QTIDocument doc = loadDocument("qti12_4questiontypes.xml"); + File exportDir = new File("/HotCoffee/QTI/today/"); + exportDir.mkdirs(); + QTI12To21Converter converter = new QTI12To21Converter(exportDir); + + converter.convert(doc); + + + + } + + private QTIDocument loadDocument(String filename) { + try(InputStream in = QTI12To21ConverterTest.class.getResourceAsStream(filename)) { + XMLParser xmlParser = new XMLParser(new IMSEntityResolver()); + Document doc = xmlParser.parse(in, true); + ParserManager parser = new ParserManager(); + return (QTIDocument)parser.parse(doc); + } catch (Exception e) { + log.error("Exception when parsing input QTI input stream for " + filename, e); + return null; + } + } +} diff --git a/src/test/java/org/olat/ims/qti21/pool/qti12_4questiontypes.xml b/src/test/java/org/olat/ims/qti21/pool/qti12_4questiontypes.xml new file mode 100644 index 0000000000000000000000000000000000000000..38c496d2bd3c26df80d1030834d339fce3ff6d5a --- /dev/null +++ b/src/test/java/org/olat/ims/qti21/pool/qti12_4questiontypes.xml @@ -0,0 +1,409 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE questestinterop SYSTEM "ims_qtiasiv1p2p1.dtd"> + +<questestinterop> + <assessment ident="test8_8_84528095851561" title="E4 - Test SR"> + <qtimetadata> + <qtimetadatafield> + <fieldlabel>qmd_assessmenttype</fieldlabel> + <fieldentry>Assessment</fieldentry> + </qtimetadatafield> + </qtimetadata> + <selection_ordering> + <selection/> + <order order_type="Sequential"/> + </selection_ordering> + <section ident="test8_8_84528095851562" title="Neue Sektion"> + <selection_ordering> + <selection/> + <order order_type="Sequential"/> + </selection_ordering> + <item ident="QTIEDIT:SCQ:8000138058" title="Neue Frage"> + <presentation> + <material> + <mattext texttype="text/html"><![CDATA[Neue Frage]]></mattext> + </material> + <response_lid ident="8000138060" rcardinality="Single" rtiming="No"> + <render_choice shuffle="No" minnumber="1" maxnumber="1"> + <flow_label class="List"> + <response_label ident="8000138063" rshuffle="Yes"> + <material> + <mattext texttype="text/html"><![CDATA[Richtige Antwort]]></mattext> + </material> + </response_label> + </flow_label> + <flow_label class="List"> + <response_label ident="8000146297" rshuffle="Yes"> + <material> + <mattext texttype="text/html"><![CDATA[Falsche Antwort]]></mattext> + </material> + </response_label> + </flow_label> + </render_choice> + </response_lid> + </presentation> + <resprocessing> + <outcomes> + <decvar varname="SCORE" vartype="Decimal" defaultval="0" minvalue="0.0" maxvalue="1.0" cutvalue="1.0"/> + </outcomes> + <respcondition title="Mastery" continue="Yes"> + <conditionvar> + <varequal respident="8000138060" case="Yes">8000138063</varequal> + </conditionvar> + <setvar varname="SCORE" action="Set">1.0</setvar> + <displayfeedback feedbacktype="Response" linkrefid="Mastery"/> + </respcondition> + <respcondition title="Fail" continue="Yes"> + <conditionvar> + <or> + <varequal respident="8000138060" case="Yes">8000146297</varequal> + </or> + </conditionvar> + <setvar varname="SCORE" action="Set">0</setvar> + <displayfeedback feedbacktype="Response" linkrefid="Fail"/> + <displayfeedback feedbacktype="Solution" linkrefid="Solution"/> + <displayfeedback feedbacktype="Hint" linkrefid="Hint"/> + </respcondition> + <respcondition title="_olat_resp_feedback" continue="Yes"> + <conditionvar> + <varequal respident="8000138060" case="Yes">8000138063</varequal> + </conditionvar> + <displayfeedback feedbacktype="Response" linkrefid="8000138063"/> + </respcondition> + <respcondition title="_olat_resp_feedback" continue="Yes"> + <conditionvar> + <varequal respident="8000138060" case="Yes">8000146297</varequal> + </conditionvar> + <displayfeedback feedbacktype="Response" linkrefid="8000146297"/> + </respcondition> + <respcondition title="Fail" continue="Yes"> + <conditionvar> + <other/> + </conditionvar> + <setvar varname="SCORE" action="Set">0</setvar> + <displayfeedback feedbacktype="Response" linkrefid="Fail"/> + <displayfeedback feedbacktype="Solution" linkrefid="Solution"/> + <displayfeedback feedbacktype="Hint" linkrefid="Hint"/> + </respcondition> + </resprocessing> + </item> + <item ident="QTIEDIT:MCQ:8000150743" title="Multiple Frage"> + <itemcontrol feedbackswitch="No" hintswitch="No" solutionswitch="No"/> + <presentation> + <material> + <mattext texttype="text/html"><![CDATA[Neue Frage]]></mattext> + </material> + <response_lid ident="8000150745" rcardinality="Multiple" rtiming="No"> + <render_choice shuffle="No" minnumber="0" maxnumber="4"> + <flow_label class="List"> + <response_label ident="8000150748" rshuffle="Yes"> + <material> + <mattext texttype="text/html"><![CDATA[Richtige Antwort]]></mattext> + </material> + </response_label> + </flow_label> + <flow_label class="List"> + <response_label ident="8000152077" rshuffle="Yes"> + <material> + <mattext texttype="text/html"><![CDATA[Falsche Antwort]]></mattext> + </material> + </response_label> + </flow_label> + <flow_label class="List"> + <response_label ident="8000152080" rshuffle="Yes"> + <material> + <mattext texttype="text/html"><![CDATA[Richtige Antwort]]></mattext> + </material> + </response_label> + </flow_label> + <flow_label class="List"> + <response_label ident="8000152091" rshuffle="Yes"> + <material> + <mattext texttype="text/html"><![CDATA[Falsche Antwort]]></mattext> + </material> + </response_label> + </flow_label> + </render_choice> + </response_lid> + </presentation> + <resprocessing> + <outcomes> + <decvar varname="SCORE" vartype="Decimal" defaultval="0" minvalue="0.0" maxvalue="1.0" cutvalue="1.0"/> + </outcomes> + <respcondition title="Mastery" continue="Yes"> + <conditionvar> + <and> + <varequal respident="8000150745" case="Yes">8000150748</varequal> + <varequal respident="8000150745" case="Yes">8000152080</varequal> + </and> + <not> + <or> + <varequal respident="8000150745" case="Yes">8000152077</varequal> + <varequal respident="8000150745" case="Yes">8000152091</varequal> + </or> + </not> + </conditionvar> + <setvar varname="SCORE" action="Set">1.0</setvar> + <displayfeedback feedbacktype="Response" linkrefid="Mastery"/> + </respcondition> + <respcondition title="Fail" continue="Yes"> + <conditionvar> + <or> + <varequal respident="8000150745" case="Yes">8000152077</varequal> + <varequal respident="8000150745" case="Yes">8000152091</varequal> + </or> + </conditionvar> + <setvar varname="SCORE" action="Set">0</setvar> + <displayfeedback feedbacktype="Response" linkrefid="Fail"/> + <displayfeedback feedbacktype="Solution" linkrefid="Solution"/> + <displayfeedback feedbacktype="Hint" linkrefid="Hint"/> + </respcondition> + <respcondition title="_olat_resp_feedback" continue="Yes"> + <conditionvar> + <varequal respident="8000150745" case="Yes">8000150748</varequal> + </conditionvar> + <displayfeedback feedbacktype="Response" linkrefid="8000150748"/> + </respcondition> + <respcondition title="_olat_resp_feedback" continue="Yes"> + <conditionvar> + <varequal respident="8000150745" case="Yes">8000152077</varequal> + </conditionvar> + <displayfeedback feedbacktype="Response" linkrefid="8000152077"/> + </respcondition> + <respcondition title="_olat_resp_feedback" continue="Yes"> + <conditionvar> + <varequal respident="8000150745" case="Yes">8000152080</varequal> + </conditionvar> + <displayfeedback feedbacktype="Response" linkrefid="8000152080"/> + </respcondition> + <respcondition title="_olat_resp_feedback" continue="Yes"> + <conditionvar> + <varequal respident="8000150745" case="Yes">8000152091</varequal> + </conditionvar> + <displayfeedback feedbacktype="Response" linkrefid="8000152091"/> + </respcondition> + <respcondition title="Fail" continue="Yes"> + <conditionvar> + <other/> + </conditionvar> + <setvar varname="SCORE" action="Set">0</setvar> + <displayfeedback feedbacktype="Response" linkrefid="Fail"/> + <displayfeedback feedbacktype="Solution" linkrefid="Solution"/> + <displayfeedback feedbacktype="Hint" linkrefid="Hint"/> + </respcondition> + </resprocessing> + <itemfeedback ident="Hint" view="All"> + <hint feedbackstyle="Incremental"> + <hintmaterial> + <material> + <mattext><![CDATA[ +]]></mattext> + </material> + </hintmaterial> + </hint> + </itemfeedback> + <itemfeedback ident="Solution" view="All"> + <solution> + <solutionmaterial> + <material> + <mattext><![CDATA[ +]]></mattext> + </material> + </solutionmaterial> + </solution> + </itemfeedback> + </item> + <item ident="QTIEDIT:KPRIM:8000171225" title="Neue Frage"> + <presentation> + <material> + <mattext texttype="text/html"><![CDATA[Neue Frage]]></mattext> + </material> + <response_lid ident="8000171227" rcardinality="Multiple" rtiming="No"> + <render_choice shuffle="No" minnumber="0" maxnumber="4"> + <flow_label class="List"> + <response_label ident="8000171230" rshuffle="Yes"> + <material> + <mattext texttype="text/html"><![CDATA[+ Antwort]]></mattext> + </material> + </response_label> + </flow_label> + <flow_label class="List"> + <response_label ident="8000171233" rshuffle="Yes"> + <material> + <mattext texttype="text/html"><![CDATA[- Antwort]]></mattext> + </material> + </response_label> + </flow_label> + <flow_label class="List"> + <response_label ident="8000171236" rshuffle="Yes"> + <material> + <mattext texttype="text/html"><![CDATA[+ Antwort]]></mattext> + </material> + </response_label> + </flow_label> + <flow_label class="List"> + <response_label ident="8000171239" rshuffle="Yes"> + <material> + <mattext texttype="text/html"><![CDATA[- Antwort]]></mattext> + </material> + </response_label> + </flow_label> + </render_choice> + </response_lid> + </presentation> + <resprocessing> + <outcomes> + <decvar varname="SCORE" vartype="Decimal" defaultval="0" minvalue="0.0" maxvalue="1.0" cutvalue="1.0"/> + </outcomes> + <respcondition continue="Yes" title="Mastery"> + <conditionvar> + <varequal respident="8000171227" case="Yes">8000171230:correct</varequal> + </conditionvar> + <setvar varname="SCORE" action="Add">0.25</setvar> + </respcondition> + <respcondition continue="Yes" title="Fail"> + <conditionvar> + <not> + <varequal respident="8000171227" case="Yes">8000171230:correct</varequal> + </not> + </conditionvar> + <setvar varname="SCORE" action="Add">-0.25</setvar> + </respcondition> + <respcondition continue="Yes" title="Mastery"> + <conditionvar> + <varequal respident="8000171227" case="Yes">8000171233:wrong</varequal> + </conditionvar> + <setvar varname="SCORE" action="Add">0.25</setvar> + </respcondition> + <respcondition continue="Yes" title="Fail"> + <conditionvar> + <not> + <varequal respident="8000171227" case="Yes">8000171233:wrong</varequal> + </not> + </conditionvar> + <setvar varname="SCORE" action="Add">-0.25</setvar> + </respcondition> + <respcondition continue="Yes" title="Mastery"> + <conditionvar> + <varequal respident="8000171227" case="Yes">8000171236:correct</varequal> + </conditionvar> + <setvar varname="SCORE" action="Add">0.25</setvar> + </respcondition> + <respcondition continue="Yes" title="Fail"> + <conditionvar> + <not> + <varequal respident="8000171227" case="Yes">8000171236:correct</varequal> + </not> + </conditionvar> + <setvar varname="SCORE" action="Add">-0.25</setvar> + </respcondition> + <respcondition continue="Yes" title="Mastery"> + <conditionvar> + <varequal respident="8000171227" case="Yes">8000171239:wrong</varequal> + </conditionvar> + <setvar varname="SCORE" action="Add">0.25</setvar> + </respcondition> + <respcondition continue="Yes" title="Fail"> + <conditionvar> + <not> + <varequal respident="8000171227" case="Yes">8000171239:wrong</varequal> + </not> + </conditionvar> + <setvar varname="SCORE" action="Add">-0.25</setvar> + </respcondition> + <respcondition title="Mastery" continue="Yes"> + <conditionvar> + <and> + <varequal respident="8000171227" case="Yes">8000171230:correct</varequal> + <varequal respident="8000171227" case="Yes">8000171233:wrong</varequal> + <varequal respident="8000171227" case="Yes">8000171236:correct</varequal> + <varequal respident="8000171227" case="Yes">8000171239:wrong</varequal> + </and> + </conditionvar> + <displayfeedback feedbacktype="Response" linkrefid="Mastery"/> + </respcondition> + <respcondition title="Fail" continue="Yes"> + <conditionvar> + <not> + <and> + <varequal respident="8000171227" case="Yes">8000171230:correct</varequal> + <varequal respident="8000171227" case="Yes">8000171233:wrong</varequal> + <varequal respident="8000171227" case="Yes">8000171236:correct</varequal> + <varequal respident="8000171227" case="Yes">8000171239:wrong</varequal> + </and> + </not> + </conditionvar> + <displayfeedback feedbacktype="Response" linkrefid="Fail"/> + <displayfeedback feedbacktype="Solution" linkrefid="Solution"/> + <displayfeedback feedbacktype="Hint" linkrefid="Hint"/> + </respcondition> + </resprocessing> + </item> + <item ident="QTIEDIT:FIB:8000173476" title="Lückentext Frage"> + <itemcontrol feedbackswitch="No" hintswitch="No" solutionswitch="No"/> + <presentation label="notset"> + <flow> + <material> + <mattext texttype="text/html"><![CDATA[<p>To be or</p>]]></mattext> + </material> + <response_str ident="8000177110" rcardinality="Single"> + <render_fib columns="3" maxchars="10"> + <flow_label class="Block"> + <response_label ident="8000177110" rshuffle="Yes"/> + </flow_label> + </render_fib> + </response_str> + <material> + <mattext texttype="text/html"><![CDATA[<p>to be</p>]]></mattext> + </material> + </flow> + </presentation> + <resprocessing> + <outcomes> + <decvar varname="SCORE" vartype="Decimal" defaultval="0" minvalue="0.0" maxvalue="1.0" cutvalue="1.0"/> + </outcomes> + <respcondition title="Mastery" continue="Yes"> + <conditionvar> + <and> + <or> + <varequal respident="8000177110" case="No"><![CDATA[not]]></varequal> + </or> + </and> + </conditionvar> + <setvar varname="SCORE" action="Set">1.0</setvar> + <displayfeedback feedbacktype="Response" linkrefid="Mastery"/> + </respcondition> + <respcondition title="Fail" continue="Yes"> + <conditionvar> + <other/> + </conditionvar> + <setvar varname="SCORE" action="Set">0</setvar> + <displayfeedback feedbacktype="Response" linkrefid="Fail"/> + <displayfeedback feedbacktype="Solution" linkrefid="Solution"/> + <displayfeedback feedbacktype="Hint" linkrefid="Hint"/> + </respcondition> + </resprocessing> + <itemfeedback ident="Hint" view="All"> + <hint feedbackstyle="Incremental"> + <hintmaterial> + <material> + <mattext><![CDATA[ +]]></mattext> + </material> + </hintmaterial> + </hint> + </itemfeedback> + <itemfeedback ident="Solution" view="All"> + <solution> + <solutionmaterial> + <material> + <mattext><![CDATA[ +]]></mattext> + </material> + </solutionmaterial> + </solution> + </itemfeedback> + </item> + </section> + </assessment> +</questestinterop>