diff --git a/src/main/java/org/olat/course/nodes/iq/IQConfigurationController.java b/src/main/java/org/olat/course/nodes/iq/IQConfigurationController.java index 24098bb12b19596b4c87d144f7b4ca2ec8220c88..3f9ad986b87a744f600fa89ed1908ad0339484d4 100644 --- a/src/main/java/org/olat/course/nodes/iq/IQConfigurationController.java +++ b/src/main/java/org/olat/course/nodes/iq/IQConfigurationController.java @@ -571,13 +571,17 @@ public class IQConfigurationController extends BasicController { private boolean needManualCorrectionQTI21(AssessmentItemRef itemRef, ResolvedAssessmentTest resolvedAssessmentTest) { ResolvedAssessmentItem resolvedAssessmentItem = resolvedAssessmentTest.getResolvedAssessmentItem(itemRef); - AssessmentItem assessmentItem = resolvedAssessmentItem.getItemLookup().getRootNodeHolder().getRootNode(); - List<Interaction> interactions = assessmentItem.getItemBody().findInteractions(); - for(Interaction interaction:interactions) { - if(interaction instanceof UploadInteraction - || interaction instanceof DrawingInteraction - || interaction instanceof ExtendedTextInteraction) { - return true; + if(resolvedAssessmentItem != null + && resolvedAssessmentItem.getItemLookup() != null + && resolvedAssessmentItem.getItemLookup().getRootNodeHolder() != null) { + AssessmentItem assessmentItem = resolvedAssessmentItem.getItemLookup().getRootNodeHolder().getRootNode(); + List<Interaction> interactions = assessmentItem.getItemBody().findInteractions(); + for(Interaction interaction:interactions) { + if(interaction instanceof UploadInteraction + || interaction instanceof DrawingInteraction + || interaction instanceof ExtendedTextInteraction) { + return true; + } } } return false; diff --git a/src/main/java/org/olat/ims/qti/export/Archive_1_SelectNodeStep.java b/src/main/java/org/olat/ims/qti/export/Archive_1_SelectNodeStep.java index 322f5b1bbcad908dbb8eeb3bc32cb3265fb7dfd3..c0ec740556976a2382ca40e16b198134cb82f609 100644 --- a/src/main/java/org/olat/ims/qti/export/Archive_1_SelectNodeStep.java +++ b/src/main/java/org/olat/ims/qti/export/Archive_1_SelectNodeStep.java @@ -36,6 +36,7 @@ import org.olat.core.gui.control.generic.wizard.StepsEvent; import org.olat.core.gui.control.generic.wizard.StepsRunContext; import org.olat.core.id.OLATResourceable; import org.olat.course.assessment.model.AssessmentNodeData; +import org.olat.ims.qti.export.QTIArchiver.Type; /** * @@ -110,7 +111,12 @@ public class Archive_1_SelectNodeStep extends BasicStep { protected void event(UserRequest ureq, Controller source, Event event) { if(source == selectCtrl) { if(event instanceof SelectTestOrSurveyEvent) { - fireEvent(ureq, StepsEvent.ACTIVATE_NEXT); + QTIArchiver archiver = ((QTIArchiver)getFromRunContext("archiver")); + if(archiver.getType() == Type.onyx || archiver.getType() == Type.qti21) { + fireEvent(ureq, StepsEvent.INFORM_FINISHED); + } else { + fireEvent(ureq, StepsEvent.ACTIVATE_NEXT); + } } } super.event(ureq, source, event); diff --git a/src/main/java/org/olat/ims/qti/export/QTIArchiver.java b/src/main/java/org/olat/ims/qti/export/QTIArchiver.java index d6ac021b02a08f740e4a7dcc72ba18dc05a3d5ec..e46ae896daa315dab40974103f506852893ad283 100644 --- a/src/main/java/org/olat/ims/qti/export/QTIArchiver.java +++ b/src/main/java/org/olat/ims/qti/export/QTIArchiver.java @@ -126,10 +126,34 @@ public class QTIArchiver { } public void setData(AssessmentNodeData data) { + this.type = null; + this.results = null; this.data = data; ICourse course = CourseFactory.loadCourse(courseOres); courseNode = course.getRunStructure().getNode(data.getIdent()); getQTIItemConfigs(); + getType(); + } + + public Type getType() { + if(type == null) { + if (courseNode.getModuleConfiguration().get(IQEditController.CONFIG_KEY_TYPE_QTI) != null) { + boolean isOnyx = courseNode.getModuleConfiguration().get(IQEditController.CONFIG_KEY_TYPE_QTI).equals(IQEditController.CONFIG_VALUE_QTI2); + if(isOnyx) { + type = Type.onyx; + } + } + + if(type != Type.onyx) { + RepositoryEntry testRe = courseNode.getReferencedRepositoryEntry(); + if(ImsQTI21Resource.TYPE_NAME.equals(testRe.getOlatResource().getResourceableTypeName())) { + type = Type.qti21; + } else { + type = Type.qti12; + } + } + } + return type; } public boolean hasResults() { diff --git a/src/main/java/org/olat/ims/qti21/manager/CorrectResponsesUtil.java b/src/main/java/org/olat/ims/qti21/manager/CorrectResponsesUtil.java index 409285777f13876edf4af0dffc1e12a1694ddc2d..e1b690b6ebd7ce6d8873875c03d963339f49e75f 100644 --- a/src/main/java/org/olat/ims/qti21/manager/CorrectResponsesUtil.java +++ b/src/main/java/org/olat/ims/qti21/manager/CorrectResponsesUtil.java @@ -25,13 +25,14 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; import org.olat.core.util.StringHelper; import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem; import uk.ac.ed.ph.jqtiplus.node.item.CorrectResponse; import uk.ac.ed.ph.jqtiplus.node.item.interaction.ChoiceInteraction; import uk.ac.ed.ph.jqtiplus.node.item.interaction.Interaction; -import uk.ac.ed.ph.jqtiplus.node.item.interaction.MatchInteraction; import uk.ac.ed.ph.jqtiplus.node.item.response.declaration.MapEntry; import uk.ac.ed.ph.jqtiplus.node.item.response.declaration.ResponseDeclaration; import uk.ac.ed.ph.jqtiplus.node.shared.FieldValue; @@ -39,7 +40,11 @@ import uk.ac.ed.ph.jqtiplus.types.Identifier; import uk.ac.ed.ph.jqtiplus.value.Cardinality; import uk.ac.ed.ph.jqtiplus.value.DirectedPairValue; import uk.ac.ed.ph.jqtiplus.value.IdentifierValue; +import uk.ac.ed.ph.jqtiplus.value.IntegerValue; import uk.ac.ed.ph.jqtiplus.value.MultipleValue; +import uk.ac.ed.ph.jqtiplus.value.OrderedValue; +import uk.ac.ed.ph.jqtiplus.value.PairValue; +import uk.ac.ed.ph.jqtiplus.value.PointValue; import uk.ac.ed.ph.jqtiplus.value.SingleValue; import uk.ac.ed.ph.jqtiplus.value.StringValue; import uk.ac.ed.ph.jqtiplus.value.Value; @@ -54,6 +59,8 @@ import uk.ac.ed.ph.jqtiplus.value.Value; */ public class CorrectResponsesUtil { + private static final OLog log = Tracing.createLoggerFor(CorrectResponsesUtil.class); + /** * Remove the leading and trailing [ ] if exists. * @param stringuifiedResponses @@ -71,6 +78,58 @@ public class CorrectResponsesUtil { return stringuifiedResponses; } + /** + * Parse response in the form [34 45][test] and return "23 45", test. + * @return + */ + public static final List<String> parseResponses(String stringuifiedResponse) { + List<String> responses = new ArrayList<>(); + if(StringHelper.containsNonWhitespace(stringuifiedResponse)) { + StringBuilder sb = new StringBuilder(); + int numOfChars = stringuifiedResponse.length(); + for(int i=0;i<numOfChars; i++) { + char ch = stringuifiedResponse.charAt(i); + if(ch == '[') { + sb = new StringBuilder(); + } else if(ch == ']') { + responses.add(sb.toString()); + } else { + sb.append(ch); + } + } + } + return responses; + } + + /** + * The method ignore the wrong formatted coordinates + * @param responses + * @return + */ + public static final List<PointValue> parseResponses(List<String> responses) { + List<PointValue> points = new ArrayList<>(); + for(String response:responses) { + if(StringHelper.containsNonWhitespace(response)) { + try { + PointValue pointValue = PointValue.parseString(response); + points.add(pointValue); + } catch (Exception e) { + log.error("", e); + } + } + } + return points; + } + + public static final int[] convertCoordinates(final List<Integer> coords) { + final int[] result = new int[coords.size()]; + for (int i = 0; i < result.length; i++) { + result[i] = coords.get(i).intValue(); + } + + return result; + } + /** * Calculate the list of correct responses found in the response of the assessed user. * @@ -137,31 +196,129 @@ public class CorrectResponsesUtil { return correctAnswers; } + public static final List<Integer> getCorrectIntegerResponses(AssessmentItem assessmentItem, Interaction interaction) { + List<Integer> correctAnswers = new ArrayList<>(5); + + ResponseDeclaration responseDeclaration = assessmentItem.getResponseDeclaration(interaction.getResponseIdentifier()); + if(responseDeclaration != null && responseDeclaration.getCorrectResponse() != null) { + CorrectResponse correctResponse = responseDeclaration.getCorrectResponse(); + if(correctResponse.getCardinality().isOneOf(Cardinality.SINGLE)) { + List<FieldValue> values = correctResponse.getFieldValues(); + Value value = FieldValue.computeValue(Cardinality.SINGLE, values); + if(value instanceof IntegerValue) { + IntegerValue identifierValue = (IntegerValue)value; + correctAnswers.add(identifierValue.intValue()); + } + + } else if(correctResponse.getCardinality().isOneOf(Cardinality.MULTIPLE)) { + Value value = FieldValue.computeValue(Cardinality.MULTIPLE, correctResponse.getFieldValues()); + if(value instanceof MultipleValue) { + MultipleValue multiValue = (MultipleValue)value; + for(SingleValue sValue:multiValue.getAll()) { + if(sValue instanceof IntegerValue) { + IntegerValue identifierValue = (IntegerValue)value; + correctAnswers.add(identifierValue.intValue()); + } + } + } + } + } + + return correctAnswers; + } + + + public static final List<Identifier> getCorrectOrderedIdentifierResponses(AssessmentItem assessmentItem, Interaction interaction) { + List<Identifier> correctAnswers = new ArrayList<>(5); + + ResponseDeclaration responseDeclaration = assessmentItem.getResponseDeclaration(interaction.getResponseIdentifier()); + if(responseDeclaration != null && responseDeclaration.getCorrectResponse() != null) { + CorrectResponse correctResponse = responseDeclaration.getCorrectResponse(); + if(correctResponse.getCardinality().isOneOf(Cardinality.ORDERED)) { + List<FieldValue> values = correctResponse.getFieldValues(); + Value value = FieldValue.computeValue(Cardinality.ORDERED, values); + if(value instanceof OrderedValue) { + OrderedValue multiValue = (OrderedValue)value; + multiValue.forEach(oValue -> { + if(oValue instanceof IdentifierValue) { + IdentifierValue identifierValue = (IdentifierValue)oValue; + Identifier correctAnswer = identifierValue.identifierValue(); + correctAnswers.add(correctAnswer); + } + + }); + } + } + } + + return correctAnswers; + } + + public static final List<String> getCorrectMultiplePairResponses(AssessmentItem assessmentItem, Interaction interaction, boolean withDelimiter) { + final List<String> correctAnswers = new ArrayList<>(5); + + ResponseDeclaration responseDeclaration = assessmentItem.getResponseDeclaration(interaction.getResponseIdentifier()); + if(responseDeclaration != null && responseDeclaration.getCorrectResponse() != null) { + CorrectResponse correctResponse = responseDeclaration.getCorrectResponse(); + if(correctResponse.getCardinality().isOneOf(Cardinality.MULTIPLE)) { + List<FieldValue> values = correctResponse.getFieldValues(); + Value value = FieldValue.computeValue(Cardinality.MULTIPLE, values); + if(value instanceof MultipleValue) { + MultipleValue multiValue = (MultipleValue)value; + multiValue.forEach(oValue -> { + if(oValue instanceof PairValue) { + PairValue pairValue = (PairValue)oValue; + String source = pairValue.sourceValue().toString(); + String destination = pairValue.destValue().toString(); + if(withDelimiter) { + correctAnswers.add("[" + source + " " + destination + "]"); + } else { + correctAnswers.add(source + " " + destination); + } + } + }); + } + } + } + return correctAnswers; + } + /** * The list of correct associations * @param assessmentItem * @param interaction * @return A list of string with [ and ] before and after! */ - public static final Set<String> getCorrectKPrimResponses(AssessmentItem assessmentItem, MatchInteraction interaction) { + public static final Set<String> getCorrectDirectPairResponses(AssessmentItem assessmentItem, Interaction interaction, boolean withDelimiter) { ResponseDeclaration responseDeclaration = assessmentItem.getResponseDeclaration(interaction.getResponseIdentifier()); - + + Set<String> correctAnswers = new HashSet<>(); //readable responses - Set<String> rightResponses = new HashSet<>(); - List<MapEntry> mapEntries = responseDeclaration.getMapping().getMapEntries(); - for(MapEntry mapEntry:mapEntries) { - SingleValue mapKey = mapEntry.getMapKey(); - if(mapKey instanceof DirectedPairValue) { - DirectedPairValue pairValue = (DirectedPairValue)mapKey; - String source = pairValue.sourceValue().toString(); - String destination = pairValue.destValue().toString(); - rightResponses.add("[" + source + " " + destination + "]"); - } + if(responseDeclaration != null && responseDeclaration.getCorrectResponse() != null) { + CorrectResponse correctResponse = responseDeclaration.getCorrectResponse(); + if(correctResponse.getCardinality().isOneOf(Cardinality.MULTIPLE)) { + List<FieldValue> values = correctResponse.getFieldValues(); + Value value = FieldValue.computeValue(Cardinality.MULTIPLE, values); + if(value instanceof MultipleValue) { + MultipleValue multiValue = (MultipleValue)value; + multiValue.forEach(oValue -> { + if(oValue instanceof DirectedPairValue) { + DirectedPairValue pairValue = (DirectedPairValue)oValue; + String source = pairValue.sourceValue().toString(); + String destination = pairValue.destValue().toString(); + if(withDelimiter) { + correctAnswers.add("[" + source + " " + destination + "]"); + } else { + correctAnswers.add(source + " " + destination); + } + } + }); + } + } } - return rightResponses; + return correctAnswers; } - public static final TextEntry getCorrectTextResponses(AssessmentItem assessmentItem, Interaction interaction) { ResponseDeclaration responseDeclaration = assessmentItem.getResponseDeclaration(interaction.getResponseIdentifier()); diff --git a/src/main/java/org/olat/ims/qti21/manager/archive/QTI21ArchiveFormat.java b/src/main/java/org/olat/ims/qti21/manager/archive/QTI21ArchiveFormat.java index 4b0130978ec645f3740e0bd4552e65ed48aaf429..467014b488f43247df334dfe5e24c97b9966165d 100644 --- a/src/main/java/org/olat/ims/qti21/manager/archive/QTI21ArchiveFormat.java +++ b/src/main/java/org/olat/ims/qti21/manager/archive/QTI21ArchiveFormat.java @@ -59,6 +59,7 @@ import org.olat.ims.qti21.manager.QTI21ServiceImpl; import org.olat.ims.qti21.manager.archive.interactions.AssociateInteractionArchive; import org.olat.ims.qti21.manager.archive.interactions.ChoiceInteractionArchive; import org.olat.ims.qti21.manager.archive.interactions.DefaultInteractionArchive; +import org.olat.ims.qti21.manager.archive.interactions.NoOutputInteractionArchive; import org.olat.ims.qti21.manager.archive.interactions.ExtendedTextInteractionArchive; import org.olat.ims.qti21.manager.archive.interactions.GapMatchInteractionArchive; import org.olat.ims.qti21.manager.archive.interactions.GraphicAssociateInteractionArchive; @@ -122,7 +123,8 @@ public class QTI21ArchiveFormat { private ResolvedAssessmentTest resolvedAssessmentTest; private List<UserPropertyHandler> userPropertyHandlers; - private final Map<String,ItemInfos> itemInfosMap = new HashMap<>(); + + private List<ItemInfos> itemInfos; private final Map<String, InteractionArchive> interactionArchiveMap = new HashMap<>(); private final QTI21Service qtiService; @@ -143,29 +145,29 @@ public class QTI21ArchiveFormat { } private void initInteractionWriters() { - interactionArchiveMap.put(AssociateInteraction.QTI_CLASS_NAME, new AssociateInteractionArchive()); - interactionArchiveMap.put(ChoiceInteraction.QTI_CLASS_NAME, new ChoiceInteractionArchive()); - interactionArchiveMap.put(DrawingInteraction.QTI_CLASS_NAME, new DefaultInteractionArchive());//like file - interactionArchiveMap.put(ExtendedTextInteraction.QTI_CLASS_NAME, new ExtendedTextInteractionArchive()); - interactionArchiveMap.put(GapMatchInteraction.QTI_CLASS_NAME, new GapMatchInteractionArchive()); - interactionArchiveMap.put(GraphicAssociateInteraction.QTI_CLASS_NAME, new GraphicAssociateInteractionArchive()); - interactionArchiveMap.put(GraphicGapMatchInteraction.QTI_CLASS_NAME, new GraphicGapMatchInteractionArchive()); - interactionArchiveMap.put(GraphicOrderInteraction.QTI_CLASS_NAME, new GraphicOrderInteractionArchive()); - interactionArchiveMap.put(HotspotInteraction.QTI_CLASS_NAME, new HotspotInteractionArchive()); - interactionArchiveMap.put(SelectPointInteraction.QTI_CLASS_NAME, new SelectPointInteractionArchive()); - interactionArchiveMap.put(HottextInteraction.QTI_CLASS_NAME, new HottextInteractionArchive()); - interactionArchiveMap.put(MatchInteraction.QTI_CLASS_NAME, new MatchInteractionArchive());//only kprim + interactionArchiveMap.put(AssociateInteraction.QTI_CLASS_NAME, new AssociateInteractionArchive()); //ok + interactionArchiveMap.put(ChoiceInteraction.QTI_CLASS_NAME, new ChoiceInteractionArchive()); //ok + interactionArchiveMap.put(DrawingInteraction.QTI_CLASS_NAME, new DefaultInteractionArchive()); //like file + interactionArchiveMap.put(ExtendedTextInteraction.QTI_CLASS_NAME, new ExtendedTextInteractionArchive()); //ok + interactionArchiveMap.put(GapMatchInteraction.QTI_CLASS_NAME, new GapMatchInteractionArchive()); //ok + interactionArchiveMap.put(GraphicAssociateInteraction.QTI_CLASS_NAME, new GraphicAssociateInteractionArchive()); //ok + interactionArchiveMap.put(GraphicGapMatchInteraction.QTI_CLASS_NAME, new GraphicGapMatchInteractionArchive()); //ok + interactionArchiveMap.put(GraphicOrderInteraction.QTI_CLASS_NAME, new GraphicOrderInteractionArchive()); //ok + interactionArchiveMap.put(HotspotInteraction.QTI_CLASS_NAME, new HotspotInteractionArchive()); //ok + interactionArchiveMap.put(SelectPointInteraction.QTI_CLASS_NAME, new SelectPointInteractionArchive()); //ok + interactionArchiveMap.put(HottextInteraction.QTI_CLASS_NAME, new HottextInteractionArchive()); //ok + interactionArchiveMap.put(MatchInteraction.QTI_CLASS_NAME, new MatchInteractionArchive()); //ok interactionArchiveMap.put(MediaInteraction.QTI_CLASS_NAME, new MediaInteractionArchive()); - interactionArchiveMap.put(OrderInteraction.QTI_CLASS_NAME, new OrderInteractionArchive()); + interactionArchiveMap.put(OrderInteraction.QTI_CLASS_NAME, new OrderInteractionArchive()); //ok interactionArchiveMap.put(PositionObjectInteraction.QTI_CLASS_NAME, new PositionObjectInteractionArchive()); - interactionArchiveMap.put(SliderInteraction.QTI_CLASS_NAME, new SliderInteractionArchive()); + interactionArchiveMap.put(SliderInteraction.QTI_CLASS_NAME, new SliderInteractionArchive()); //ok interactionArchiveMap.put(UploadInteraction.QTI_CLASS_NAME, new DefaultInteractionArchive()); //custom - interactionArchiveMap.put(CustomInteraction.QTI_CLASS_NAME, new DefaultInteractionArchive()); + interactionArchiveMap.put(CustomInteraction.QTI_CLASS_NAME, new NoOutputInteractionArchive()); //ok //inline - interactionArchiveMap.put(EndAttemptInteraction.QTI_CLASS_NAME, new DefaultInteractionArchive());//not really usefull - interactionArchiveMap.put(InlineChoiceInteraction.QTI_CLASS_NAME, new InlineChoiceInteractionArchive()); - interactionArchiveMap.put(TextEntryInteraction.QTI_CLASS_NAME, new TextEntryInteractionArchive()); + interactionArchiveMap.put(EndAttemptInteraction.QTI_CLASS_NAME, new NoOutputInteractionArchive()); //ok + interactionArchiveMap.put(InlineChoiceInteraction.QTI_CLASS_NAME, new InlineChoiceInteractionArchive()); //ok + interactionArchiveMap.put(TextEntryInteraction.QTI_CLASS_NAME, new TextEntryInteractionArchive()); //ok } public boolean hasResults(RepositoryEntry courseEntry, String subIdent, RepositoryEntry testEntry) { @@ -216,18 +218,14 @@ public class QTI21ArchiveFormat { } col += 5;// homepage -> test duration - List<AssessmentItemRef> itemRefs = getAssessmentItemRefs(); - for(int i=0; i<itemRefs.size(); i++) { - AssessmentItemRef itemRef = itemRefs.get(i); - ResolvedAssessmentItem resolvedAssessmentItem = resolvedAssessmentTest.getResolvedAssessmentItem(itemRef); - AssessmentItem item = resolvedAssessmentItem.getRootNodeLookup().extractIfSuccessful(); - - ItemInfos itemInfos = getItemInfos(itemRef); - List<Interaction> interactions = itemInfos.getInteractions(); + List<ItemInfos> infos = getItemInfos(); + for(int i=0; i<infos.size(); i++) { + ItemInfos item = infos.get(i); + List<Interaction> interactions = item.getInteractions(); for(int j=0; j<interactions.size(); j++) { Interaction interaction = interactions.get(j); col = interactionArchiveMap.get(interaction.getQtiClassName()) - .writeHeader1(item, interaction, i, j, header1Row, col, workbook); + .writeHeader1(item.getAssessmentItem(), interaction, i, j, header1Row, col, workbook); } col += 3;//score, start, duration } @@ -256,16 +254,15 @@ public class QTI21ArchiveFormat { //header2Row.addCell(col++, translator.translate("column.header.ipaddress"), headerStyle); header2Row.addCell(col++, translator.translate("column.header.date"), headerStyle); header2Row.addCell(col++, translator.translate("column.header.duration"), headerStyle); - - List<AssessmentItemRef> itemRefs = getAssessmentItemRefs(); - for(int i=0; i<itemRefs.size(); i++) { - AssessmentItemRef itemRef = itemRefs.get(i); - ItemInfos itemInfos = getItemInfos(itemRef); - List<Interaction> interactions = itemInfos.getInteractions(); + + List<ItemInfos> infos = getItemInfos(); + for(int i=0; i<infos.size(); i++) { + ItemInfos info = infos.get(i); + List<Interaction> interactions = info.getInteractions(); for(int j=0; j<interactions.size(); j++) { Interaction interaction = interactions.get(j); col = interactionArchiveMap.get(interaction.getQtiClassName()) - .writeHeader2(interaction, i, j, header2Row, col, workbook); + .writeHeader2(info.getAssessmentItem(), interaction, i, j, header2Row, col, workbook); } header2Row.addCell(col++, translator.translate("item.score"), headerStyle); @@ -329,22 +326,21 @@ public class QTI21ArchiveFormat { //dataRow.addCell(col++, "0.0.0.1", null); dataRow.addCell(col++, testSession.getCreationDate(), workbook.getStyles().getDateStyle()); dataRow.addCell(col++, toDurationInMilliseconds(testSession.getDuration()), null); - - List<AssessmentItemRef> itemRefs = getAssessmentItemRefs(); - for(int i=0; i<itemRefs.size(); i++) { - AssessmentItemRef itemRef = itemRefs.get(i); + + List<ItemInfos> infos = getItemInfos(); + for(int i=0; i<infos.size(); i++) { + ItemInfos info = infos.get(i); + AssessmentItemRef itemRef = info.getAssessmentItemRef(); String itemRefIdentifier = itemRef.getIdentifier().toString(); AssessmentItemSession itemSession = responses.getItemSession(itemRefIdentifier); - ItemInfos itemInfos = getItemInfos(itemRef); - AssessmentItem item = itemInfos.getAssessmentItem(); - - List<Interaction> interactions = itemInfos.getInteractions(); + + List<Interaction> interactions = info.getInteractions(); for(int j=0; j<interactions.size(); j++) { Interaction interaction = interactions.get(j); AssessmentResponse response = responses .getResponse(itemRefIdentifier, interaction.getResponseIdentifier()); col = interactionArchiveMap.get(interaction.getQtiClassName()) - .writeInteractionData(item, response, interaction, j, dataRow, col, workbook); + .writeInteractionData(info.getAssessmentItem(), response, interaction, j, dataRow, col, workbook); } //score, start, duration @@ -363,18 +359,17 @@ public class QTI21ArchiveFormat { return value.longValue() / 1000l; } - private List<AssessmentItemRef> getAssessmentItemRefs() { - return resolvedAssessmentTest.getAssessmentItemRefs(); - } - - private ItemInfos getItemInfos(AssessmentItemRef itemRef) { - String itemRefIdentifier = itemRef.getIdentifier().toString(); - ItemInfos itemInfos = itemInfosMap.get(itemRefIdentifier); + private List<ItemInfos> getItemInfos() { if(itemInfos == null) { - ResolvedAssessmentItem resolvedItem = resolvedAssessmentTest.getResolvedAssessmentItem(itemRef); - AssessmentItem item = resolvedItem.getRootNodeLookup().extractIfSuccessful(); - itemInfos = new ItemInfos(item, item.getItemBody().findInteractions()); - itemInfosMap.put(itemRefIdentifier, itemInfos); + itemInfos = new ArrayList<>(); + List<AssessmentItemRef> itemRefs = resolvedAssessmentTest.getAssessmentItemRefs(); + for(AssessmentItemRef itemRef:itemRefs) { + ResolvedAssessmentItem resolvedItem = resolvedAssessmentTest.getResolvedAssessmentItem(itemRef); + AssessmentItem item = resolvedItem.getRootNodeLookup().extractIfSuccessful(); + if(item != null) { + itemInfos.add(new ItemInfos(itemRef, item, item.getItemBody().findInteractions())); + } + } } return itemInfos; } @@ -430,14 +425,20 @@ public class QTI21ArchiveFormat { private static class ItemInfos { + private final AssessmentItemRef itemRef; private final AssessmentItem assessmentItem; private final List<Interaction> interactions; - public ItemInfos(AssessmentItem assessmentItem, List<Interaction> interactions) { + public ItemInfos(AssessmentItemRef itemRef, AssessmentItem assessmentItem, List<Interaction> interactions) { + this.itemRef = itemRef; this.interactions = interactions; this.assessmentItem = assessmentItem; } + public AssessmentItemRef getAssessmentItemRef() { + return itemRef; + } + public AssessmentItem getAssessmentItem() { return assessmentItem; } diff --git a/src/main/java/org/olat/ims/qti21/manager/archive/SimpleContentRenderer.java b/src/main/java/org/olat/ims/qti21/manager/archive/SimpleContentRenderer.java new file mode 100644 index 0000000000000000000000000000000000000000..cbae8569a51e2d3b740de68727e04dd8827dad6a --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/manager/archive/SimpleContentRenderer.java @@ -0,0 +1,51 @@ +/** + * <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.archive; + +import java.util.List; + +import uk.ac.ed.ph.jqtiplus.node.QtiNode; +import uk.ac.ed.ph.jqtiplus.node.content.basic.FlowStatic; +import uk.ac.ed.ph.jqtiplus.node.content.basic.TextRun; + +/** + * + * Initial date: 28.04.2016<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class SimpleContentRenderer { + + public static final String renderFlowStatics(List<FlowStatic> flowStatics) { + StringBuilder sb = new StringBuilder(); + for(FlowStatic flowStatic:flowStatics) { + render(sb, flowStatic); + } + + return sb.toString(); + } + + private static void render(StringBuilder sb, QtiNode node) { + if(node instanceof TextRun) { + sb.append(((TextRun)node).getTextContent()); + } + } + +} diff --git a/src/main/java/org/olat/ims/qti21/manager/archive/interactions/AbstractAssociateInteractionArchive.java b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/AbstractAssociateInteractionArchive.java new file mode 100644 index 0000000000000000000000000000000000000000..d6be6c0aef1fad1c1fbd7aa735865c235f03404c --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/AbstractAssociateInteractionArchive.java @@ -0,0 +1,108 @@ +/** + * <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.archive.interactions; + +import java.util.List; + +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.olat.core.util.openxml.OpenXMLWorkbook; +import org.olat.core.util.openxml.OpenXMLWorksheet.Row; +import org.olat.ims.qti21.AssessmentResponse; +import org.olat.ims.qti21.manager.CorrectResponsesUtil; + +import uk.ac.ed.ph.jqtiplus.internal.util.Pair; +import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem; +import uk.ac.ed.ph.jqtiplus.node.item.interaction.Interaction; +import uk.ac.ed.ph.jqtiplus.types.DataTypeBinder; +import uk.ac.ed.ph.jqtiplus.types.Identifier; + +/** + * + * Initial date: 28.04.2016<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +abstract class AbstractAssociateInteractionArchive extends DefaultInteractionArchive { + + private static final OLog log = Tracing.createLoggerFor(AbstractAssociateInteractionArchive.class); + + @Override + public int writeHeader1(AssessmentItem item, Interaction interaction, int itemNumber, int interactionNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { + int maxAssociation = getMaxAssociations(interaction); + if(interactionNumber == 0) { + String header = item.getTitle(); + dataRow.addCell(col, header, workbook.getStyles().getHeaderStyle()); + } + col += Math.max(1, maxAssociation); + return col; + } + + @Override + public int writeHeader2(AssessmentItem item, Interaction interaction, int itemNumber, int interactionNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { + int maxAssociation = getMaxAssociations(interaction); + if(maxAssociation > 0) { + for(int i=0; i<maxAssociation; i++) { + String header = (itemNumber + 1) + "_A" + i; + dataRow.addCell(col++, header, workbook.getStyles().getHeaderStyle()); + } + } else { + col++; + } + return col; + } + + @Override + public int writeInteractionData(AssessmentItem item, AssessmentResponse response, Interaction interaction, + int itemNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { + int maxAssociation = getMaxAssociations(interaction); + + String stringuifiedResponse = response == null ? null : response.getStringuifiedResponse(); + List<String> responsesAssociations = CorrectResponsesUtil.parseResponses(stringuifiedResponse); + List<String> correctAnswers = CorrectResponsesUtil.getCorrectMultiplePairResponses(item, interaction, false); + + for(int i=0; i<maxAssociation; i++) { + String association = null; + String reverseAssociation = null; + if(responsesAssociations.size() > i) { + association = responsesAssociations.get(i); + try { + Pair<Identifier, Identifier> pair = DataTypeBinder.parsePair(association); + reverseAssociation = pair.getSecond() + " " + pair.getFirst(); + } catch (Exception e) { + log.error("", e); + association = null; + } + } + + if(association == null) { + col++; + } else if(correctAnswers.contains(association) || correctAnswers.contains(reverseAssociation)) { + dataRow.addCell(col++, association, workbook.getStyles().getCorrectStyle()); + } else { + dataRow.addCell(col++, association, null); + } + } + return col; + } + + protected abstract int getMaxAssociations(Interaction associateInteraction); + +} diff --git a/src/main/java/org/olat/ims/qti21/manager/archive/interactions/AbstractChoiceInteractionArchive.java b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/AbstractChoiceInteractionArchive.java index 516d942172646525a67aed36d6ece03bf6a37902..625d5c5ef687257ca2c295968e8350e07108f30a 100644 --- a/src/main/java/org/olat/ims/qti21/manager/archive/interactions/AbstractChoiceInteractionArchive.java +++ b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/AbstractChoiceInteractionArchive.java @@ -55,7 +55,7 @@ public abstract class AbstractChoiceInteractionArchive extends DefaultInteractio } @Override - public int writeHeader2(Interaction interaction, int itemNumber, int interactionNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { + public int writeHeader2(AssessmentItem item, Interaction interaction, int itemNumber, int interactionNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { List<? extends Choice> choices = getChoices(interaction); for(int i=0; i<choices.size(); i++) { String header = (itemNumber + 1) + "_C" + i; diff --git a/src/main/java/org/olat/ims/qti21/manager/archive/interactions/AssociateInteractionArchive.java b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/AssociateInteractionArchive.java index 23fb16ae43fa72c9b744e95188d229b14fb752ff..18c463537bf38cc5a8141340094cc1ffbd682cad 100644 --- a/src/main/java/org/olat/ims/qti21/manager/archive/interactions/AssociateInteractionArchive.java +++ b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/AssociateInteractionArchive.java @@ -19,12 +19,25 @@ */ package org.olat.ims.qti21.manager.archive.interactions; +import uk.ac.ed.ph.jqtiplus.node.item.interaction.AssociateInteraction; +import uk.ac.ed.ph.jqtiplus.node.item.interaction.Interaction; + /** * * Initial date: 26.04.2016<br> * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ -public class AssociateInteractionArchive extends DefaultInteractionArchive { +public class AssociateInteractionArchive extends AbstractAssociateInteractionArchive { + + @Override + protected int getMaxAssociations(Interaction interaction) { + AssociateInteraction associateInteraction = (AssociateInteraction)interaction; + int max = associateInteraction.getMaxAssociations(); + if(max == 0) { + max = associateInteraction.getSimpleAssociableChoices().size() * (associateInteraction.getSimpleAssociableChoices().size() - 1); + } + return max; + } } diff --git a/src/main/java/org/olat/ims/qti21/manager/archive/interactions/DefaultInteractionArchive.java b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/DefaultInteractionArchive.java index 9210931c339b660ec5891d656caec2198ab7d315..c62b38af50b431585e0664265c8f8bc0b1eae48d 100644 --- a/src/main/java/org/olat/ims/qti21/manager/archive/interactions/DefaultInteractionArchive.java +++ b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/DefaultInteractionArchive.java @@ -46,7 +46,7 @@ public class DefaultInteractionArchive implements InteractionArchive { } @Override - public int writeHeader2(Interaction interaction, int itemNumber, int interactionNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { + public int writeHeader2(AssessmentItem item, Interaction interaction, int itemNumber, int interactionNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { String header = (itemNumber + 1) + "_U" + (interactionNumber + 1);; dataRow.addCell(col++, header, workbook.getStyles().getHeaderStyle()); return col; @@ -54,7 +54,7 @@ public class DefaultInteractionArchive implements InteractionArchive { @Override public int writeInteractionData(AssessmentItem item, AssessmentResponse response, Interaction interaction, int itemNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { - String choosed = response.getStringuifiedResponse(); + String choosed = response == null ? null : response.getStringuifiedResponse(); dataRow.addCell(col++, choosed, null); return col; } diff --git a/src/main/java/org/olat/ims/qti21/manager/archive/interactions/GapMatchInteractionArchive.java b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/GapMatchInteractionArchive.java index 010d8f26539cbb464c090732b8ae13048566668e..6172d9bc1d7836533acd38de8f7910af8355d5a0 100644 --- a/src/main/java/org/olat/ims/qti21/manager/archive/interactions/GapMatchInteractionArchive.java +++ b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/GapMatchInteractionArchive.java @@ -19,13 +19,24 @@ */ package org.olat.ims.qti21.manager.archive.interactions; +import java.util.List; +import java.util.Set; + import org.olat.core.util.openxml.OpenXMLWorkbook; import org.olat.core.util.openxml.OpenXMLWorksheet.Row; import org.olat.ims.qti21.AssessmentResponse; +import org.olat.ims.qti21.manager.CorrectResponsesUtil; +import uk.ac.ed.ph.jqtiplus.node.content.basic.TextRun; +import uk.ac.ed.ph.jqtiplus.node.content.variable.TextOrVariable; import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem; import uk.ac.ed.ph.jqtiplus.node.item.interaction.GapMatchInteraction; import uk.ac.ed.ph.jqtiplus.node.item.interaction.Interaction; +import uk.ac.ed.ph.jqtiplus.node.item.interaction.choice.GapChoice; +import uk.ac.ed.ph.jqtiplus.node.item.interaction.choice.GapText; +import uk.ac.ed.ph.jqtiplus.node.item.interaction.content.Gap; +import uk.ac.ed.ph.jqtiplus.types.Identifier; +import uk.ac.ed.ph.jqtiplus.utils.QueryUtils; /** * @@ -35,12 +46,98 @@ import uk.ac.ed.ph.jqtiplus.node.item.interaction.Interaction; */ public class GapMatchInteractionArchive extends DefaultInteractionArchive { + @Override + public int writeHeader1(AssessmentItem item, Interaction interaction, int itemNumber, int interactionNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { + if(interactionNumber == 0) { + String header = item.getTitle(); + dataRow.addCell(col, header, workbook.getStyles().getHeaderStyle()); + } + col += Math.max(1, getGaps(interaction).size()); + return col; + } + + @Override + public int writeHeader2(AssessmentItem item, Interaction interaction, int itemNumber, int interactionNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { + List<Gap> gaps = getGaps(interaction); + if(gaps.size() > 0) { + for(int i=0; i<gaps.size(); i++) { + String header = (itemNumber + 1) + "_G" + (i + 1); + dataRow.addCell(col++, header, workbook.getStyles().getHeaderStyle()); + } + } else { + col++; + } + return col; + } + @Override public int writeInteractionData(AssessmentItem item, AssessmentResponse response, Interaction interaction, int itemNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { - GapMatchInteraction gapMatchInteraction = (GapMatchInteraction)interaction; - // choices but really associable choices - return super.writeInteractionData(item, response, gapMatchInteraction, itemNumber, dataRow, col, workbook); + + List<Gap> gaps = getGaps(interaction); + if(gaps.size() > 0) { + GapMatchInteraction gapMatchInteraction = (GapMatchInteraction)interaction; + + Set<String> correctAnswers = CorrectResponsesUtil.getCorrectDirectPairResponses(item, interaction, false); + String stringuifiedResponse = response == null ? null : response.getStringuifiedResponse(); + List<String> responses = CorrectResponsesUtil.parseResponses(stringuifiedResponse); + + for(Gap gap:gaps) { + String gapIdentifier = gap.getIdentifier().toString(); + + String gapResponse = null; + for(String r:responses) { + if(r.endsWith(gapIdentifier)) { + gapResponse = r; + } + } + + if(gapResponse != null) { + String[] gapResponsePair = gapResponse.split(" "); + if(gapResponsePair.length > 1) { + String gapResponseAssociation = gapResponsePair[0]; + Identifier gapResponseIdentifier = Identifier.assumedLegal(gapResponseAssociation); + GapChoice choice = gapMatchInteraction.getGapChoice(gapResponseIdentifier); + String value = null; + if(choice != null) { + value = getTextContent(choice); + } + + boolean correct = correctAnswers.contains(gapResponse); + if(correct) { + dataRow.addCell(col++, value, workbook.getStyles().getCorrectStyle()); + } else { + dataRow.addCell(col++, value); + } + } else { + col++; + } + } else { + col++; + } + } + + } else { + col++; + } + + return col; + } + + private String getTextContent(GapChoice selectedChoice) { + if(selectedChoice instanceof GapText) { + List<TextOrVariable> values = ((GapText)selectedChoice).getTextOrVariables(); + for(TextOrVariable value:values) { + if(value instanceof TextRun) { + return ((TextRun)value).getTextContent(); + } + } + } + return selectedChoice.getIdentifier().toString(); + } + + public List<Gap> getGaps(Interaction interaction) { + List<Gap> gaps = QueryUtils.search(Gap.class, interaction.getNodeGroups().getBlockStaticGroup().getBlockStatics()); + return gaps; } - } diff --git a/src/main/java/org/olat/ims/qti21/manager/archive/interactions/GraphicAssociateInteractionArchive.java b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/GraphicAssociateInteractionArchive.java index f121fd11091d1272e7622ae9af0c7d7cf167c96e..1a45b5bd0b5b7561988a6a5f505e479b3a10d474 100644 --- a/src/main/java/org/olat/ims/qti21/manager/archive/interactions/GraphicAssociateInteractionArchive.java +++ b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/GraphicAssociateInteractionArchive.java @@ -19,11 +19,6 @@ */ package org.olat.ims.qti21.manager.archive.interactions; -import org.olat.core.util.openxml.OpenXMLWorkbook; -import org.olat.core.util.openxml.OpenXMLWorksheet.Row; -import org.olat.ims.qti21.AssessmentResponse; - -import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem; import uk.ac.ed.ph.jqtiplus.node.item.interaction.GraphicAssociateInteraction; import uk.ac.ed.ph.jqtiplus.node.item.interaction.Interaction; @@ -33,15 +28,15 @@ import uk.ac.ed.ph.jqtiplus.node.item.interaction.Interaction; * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ -public class GraphicAssociateInteractionArchive extends DefaultInteractionArchive { +public class GraphicAssociateInteractionArchive extends AbstractAssociateInteractionArchive { @Override - public int writeInteractionData(AssessmentItem item, AssessmentResponse response, Interaction interaction, - int itemNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { + protected int getMaxAssociations(Interaction interaction) { GraphicAssociateInteraction associateInteraction = (GraphicAssociateInteraction)interaction; - associateInteraction.getAssociableHotspots(); - // choices but really associable choices (hotspot) GraphicGapMatchInteraction - return super.writeInteractionData(item, response, associateInteraction, itemNumber, dataRow, col, workbook); + int max = associateInteraction.getMaxAssociations(); + if(max == 0) { + max = associateInteraction.getAssociableHotspots().size() * (associateInteraction.getAssociableHotspots().size() - 1); + } + return max; } - -} +} \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti21/manager/archive/interactions/GraphicGapMatchInteractionArchive.java b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/GraphicGapMatchInteractionArchive.java index 59a4f4ebd6c97e5725b200214587bc8cf2b00178..41582e0f59bcba680eaaa0eb4e6e884603c9d132 100644 --- a/src/main/java/org/olat/ims/qti21/manager/archive/interactions/GraphicGapMatchInteractionArchive.java +++ b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/GraphicGapMatchInteractionArchive.java @@ -19,13 +19,18 @@ */ package org.olat.ims.qti21.manager.archive.interactions; +import java.util.List; +import java.util.Set; + import org.olat.core.util.openxml.OpenXMLWorkbook; import org.olat.core.util.openxml.OpenXMLWorksheet.Row; import org.olat.ims.qti21.AssessmentResponse; +import org.olat.ims.qti21.manager.CorrectResponsesUtil; import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem; import uk.ac.ed.ph.jqtiplus.node.item.interaction.GraphicGapMatchInteraction; import uk.ac.ed.ph.jqtiplus.node.item.interaction.Interaction; +import uk.ac.ed.ph.jqtiplus.node.item.interaction.graphic.AssociableHotspot; /** * @@ -34,13 +39,77 @@ import uk.ac.ed.ph.jqtiplus.node.item.interaction.Interaction; * */ public class GraphicGapMatchInteractionArchive extends DefaultInteractionArchive { - + + @Override + public int writeHeader1(AssessmentItem item, Interaction interaction, int itemNumber, int interactionNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { + if(interactionNumber == 0) { + String header = item.getTitle(); + dataRow.addCell(col++, header, workbook.getStyles().getHeaderStyle()); + } + + GraphicGapMatchInteraction gapMatchInteraction = (GraphicGapMatchInteraction)interaction; + List<AssociableHotspot> spots = gapMatchInteraction.getAssociableHotspots(); + if(spots.size() > 0) { + col += (spots.size() - 1); + } + return col; + } + + @Override + public int writeHeader2(AssessmentItem item, Interaction interaction, int itemNumber, int interactionNumber, + Row dataRow, int col, OpenXMLWorkbook workbook) { + + GraphicGapMatchInteraction gapMatchInteraction = (GraphicGapMatchInteraction)interaction; + List<AssociableHotspot> spots = gapMatchInteraction.getAssociableHotspots(); + if(spots.size() > 0) { + for(int i=0; i<spots.size(); i++) { + String header = (itemNumber + 1) + "_GG" + (i + 1); + dataRow.addCell(col++, header, workbook.getStyles().getHeaderStyle()); + } + } else { + col++; + } + return col; + } + @Override public int writeInteractionData(AssessmentItem item, AssessmentResponse response, Interaction interaction, int itemNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { GraphicGapMatchInteraction gapMatchInteraction = (GraphicGapMatchInteraction)interaction; - // choices but really associable choices (hotspot) like GraphicAssociateInteraction - return super.writeInteractionData(item, response, gapMatchInteraction, itemNumber, dataRow, col, workbook); - } + + String stringuifiedResponse = response == null ? null : response.getStringuifiedResponse(); + List<String> responses = CorrectResponsesUtil.parseResponses(stringuifiedResponse); + + Set<String> rightAnswers = CorrectResponsesUtil.getCorrectDirectPairResponses(item, interaction, false); + List<AssociableHotspot> spots = gapMatchInteraction.getAssociableHotspots(); + for(AssociableHotspot spot:spots) { + String spotIdentifier = spot.getIdentifier().toString(); + + String spotResponse = null; + for(String r:responses) { + if(r.endsWith(spotIdentifier)) { + spotResponse = r; + } + } + + if(spotResponse != null) { + String[] spotResponsePair = spotResponse.split(" "); + if(spotResponsePair.length > 1) { + String stopResponseAssociation = spotResponsePair[0]; + boolean correct = rightAnswers.contains(spotResponse); + if(correct) { + dataRow.addCell(col++, stopResponseAssociation, workbook.getStyles().getCorrectStyle()); + } else { + dataRow.addCell(col++, stopResponseAssociation); + } + } else { + col++; + } + } else { + col++; + } + } + return col; + } } diff --git a/src/main/java/org/olat/ims/qti21/manager/archive/interactions/GraphicOrderInteractionArchive.java b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/GraphicOrderInteractionArchive.java index b1409fa78cc3b7d748ec98d545f673d97acf2185..00dcb842139e136d2942ae22068491dd186d64d6 100644 --- a/src/main/java/org/olat/ims/qti21/manager/archive/interactions/GraphicOrderInteractionArchive.java +++ b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/GraphicOrderInteractionArchive.java @@ -21,9 +21,16 @@ package org.olat.ims.qti21.manager.archive.interactions; import java.util.List; +import org.olat.core.util.openxml.OpenXMLWorkbook; +import org.olat.core.util.openxml.OpenXMLWorksheet.Row; +import org.olat.ims.qti21.AssessmentResponse; +import org.olat.ims.qti21.manager.CorrectResponsesUtil; + +import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem; import uk.ac.ed.ph.jqtiplus.node.item.interaction.GraphicOrderInteraction; import uk.ac.ed.ph.jqtiplus.node.item.interaction.Interaction; -import uk.ac.ed.ph.jqtiplus.node.item.interaction.choice.Choice; +import uk.ac.ed.ph.jqtiplus.node.item.interaction.graphic.HotspotChoice; +import uk.ac.ed.ph.jqtiplus.types.Identifier; /** * @@ -31,12 +38,67 @@ import uk.ac.ed.ph.jqtiplus.node.item.interaction.choice.Choice; * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ -public class GraphicOrderInteractionArchive extends AbstractChoiceInteractionArchive { +public class GraphicOrderInteractionArchive extends DefaultInteractionArchive { + + @Override + public int writeHeader1(AssessmentItem item, Interaction interaction, int itemNumber, int interactionNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { + if(interactionNumber == 0) { + String header = item.getTitle(); + dataRow.addCell(col++, header, workbook.getStyles().getHeaderStyle()); + } + + GraphicOrderInteraction orderInteraction = (GraphicOrderInteraction)interaction; + List<HotspotChoice> choices = orderInteraction.getHotspotChoices(); + if(choices.size() > 0) { + col += (choices.size() - 1); + } + return col; + } @Override - public List<? extends Choice> getChoices(Interaction interaction) { + public int writeHeader2(AssessmentItem item, Interaction interaction, int itemNumber, int interactionNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { GraphicOrderInteraction orderInteraction = (GraphicOrderInteraction)interaction; - return orderInteraction.getHotspotChoices(); + List<HotspotChoice> choices = orderInteraction.getHotspotChoices(); + if(choices.size() > 0) { + for(int i=0; i<choices.size(); i++) { + String header = (itemNumber + 1) + "_GO" + i; + dataRow.addCell(col++, header, workbook.getStyles().getHeaderStyle()); + } + } else { + col++; + } + return col; } + @Override + public int writeInteractionData(AssessmentItem item, AssessmentResponse response, Interaction interaction, int itemNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { + GraphicOrderInteraction orderInteraction = (GraphicOrderInteraction)interaction; + + List<HotspotChoice> choices = orderInteraction.getHotspotChoices(); + if(choices.size() > 0) { + String stringuifiedResponse = response == null ? null : response.getStringuifiedResponse(); + List<String> responses = CorrectResponsesUtil.parseResponses(stringuifiedResponse); + List<Identifier> correctAnswers = CorrectResponsesUtil.getCorrectOrderedIdentifierResponses(item, interaction); + + for(int i=0; i<choices.size(); i++) { + String currentResponse = null; + if(responses.size() > i) { + currentResponse = responses.get(i); + } + String correctAnswer = null; + if(correctAnswers.size() > i) { + correctAnswer = correctAnswers.get(i).toString(); + } + + if(correctAnswer != null && correctAnswer.equals(currentResponse)) { + dataRow.addCell(col++, currentResponse, workbook.getStyles().getCorrectStyle()); + } else { + dataRow.addCell(col++, currentResponse, null); + } + } + } else { + col++; + } + return col; + } } diff --git a/src/main/java/org/olat/ims/qti21/manager/archive/interactions/HottextInteractionArchive.java b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/HottextInteractionArchive.java index 2e92364b6e2329b53984abc26a6b95dc51222ca1..0746009af644be825f63809ed371ae648f890178 100644 --- a/src/main/java/org/olat/ims/qti21/manager/archive/interactions/HottextInteractionArchive.java +++ b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/HottextInteractionArchive.java @@ -19,13 +19,21 @@ */ package org.olat.ims.qti21.manager.archive.interactions; +import java.util.List; + import org.olat.core.util.openxml.OpenXMLWorkbook; import org.olat.core.util.openxml.OpenXMLWorksheet.Row; import org.olat.ims.qti21.AssessmentResponse; +import org.olat.ims.qti21.manager.CorrectResponsesUtil; +import uk.ac.ed.ph.jqtiplus.node.QtiNode; +import uk.ac.ed.ph.jqtiplus.node.content.basic.InlineStatic; +import uk.ac.ed.ph.jqtiplus.node.content.basic.TextRun; import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem; -import uk.ac.ed.ph.jqtiplus.node.item.interaction.HottextInteraction; import uk.ac.ed.ph.jqtiplus.node.item.interaction.Interaction; +import uk.ac.ed.ph.jqtiplus.node.item.interaction.content.Hottext; +import uk.ac.ed.ph.jqtiplus.types.Identifier; +import uk.ac.ed.ph.jqtiplus.utils.QueryUtils; /** * @@ -34,14 +42,88 @@ import uk.ac.ed.ph.jqtiplus.node.item.interaction.Interaction; * */ public class HottextInteractionArchive extends DefaultInteractionArchive { - + @Override - public int writeInteractionData(AssessmentItem item, AssessmentResponse response, Interaction interaction, - int itemNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { - HottextInteraction hottextInteraction = (HottextInteraction)interaction; - //scan for Hottext which are choices - return super.writeInteractionData(item, response, hottextInteraction, itemNumber, dataRow, col, workbook); + public int writeHeader1(AssessmentItem item, Interaction interaction, int itemNumber, int interactionNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { + int numOfChoices = getHottexts(interaction).size(); + if(interactionNumber == 0) { + String header = item.getTitle(); + dataRow.addCell(col, header, workbook.getStyles().getHeaderStyle()); + } + col += Math.max(1, numOfChoices); + return col; } + + @Override + public int writeHeader2(AssessmentItem item, Interaction interaction, int itemNumber, int interactionNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { + List<Hottext> texts = getHottexts(interaction); + if(texts.size() > 0) { + for(int i=0; i<texts.size(); i++) { + String header = (itemNumber + 1) + "_HT" + (i + 1); + dataRow.addCell(col++, header, workbook.getStyles().getHeaderStyle()); + } + } else { + col++; + } + return col; + } + + @Override + public int writeInteractionData(AssessmentItem item, AssessmentResponse response, Interaction interaction, int itemNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { + String stringuifiedResponse = response == null ? null : response.getStringuifiedResponse(); + List<String> responses = CorrectResponsesUtil.parseResponses(stringuifiedResponse); + List<Identifier> correctAnswers = CorrectResponsesUtil.getCorrectIdentifierResponses(item, interaction); + + List<Hottext> texts = getHottexts(interaction); + for(int i=0; i<texts.size(); i++) { + Hottext text = texts.get(i); + Identifier textIdentifier = text.getIdentifier(); + //user select this hot text + if(responses.contains(text.getIdentifier().toString())) { + String content = getContent(text); + //correct -> must be clicked + boolean correct = correctAnswers.contains(textIdentifier); + if(correct) { + dataRow.addCell(col++, content, workbook.getStyles().getCorrectStyle()); + } else { + dataRow.addCell(col++, content, null); + } + } else { + //correct -> must not be clicked + boolean correct = !correctAnswers.contains(textIdentifier); + if(correct) { + dataRow.addCell(col++, "", workbook.getStyles().getCorrectStyle()); + } else { + col++; + } + } + } + return col; + } + + private List<Hottext> getHottexts(Interaction interaction) { + List<Hottext> texts = QueryUtils.search(Hottext.class, interaction); + return texts; + } + + private String getContent(Hottext association) { + StringBuilder sb = new StringBuilder(); + List<InlineStatic> inlineStatics = association.getInlineStatics(); + for(InlineStatic inlineStatic:inlineStatics) { + renderContent(sb, inlineStatic); + } + + if(sb.length() > 0) { + return sb.toString(); + } + return association.getIdentifier().toString(); + } + + private void renderContent(StringBuilder sb, QtiNode node) { + if(node instanceof TextRun) { + sb.append(((TextRun)node).getTextContent()); + } + } } diff --git a/src/main/java/org/olat/ims/qti21/manager/archive/interactions/InlineChoiceInteractionArchive.java b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/InlineChoiceInteractionArchive.java index e3876e3e8a25edf09547d06e7d55322a8661f695..ade3b72fc70caf6fab372547d904c228a2759a2a 100644 --- a/src/main/java/org/olat/ims/qti21/manager/archive/interactions/InlineChoiceInteractionArchive.java +++ b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/InlineChoiceInteractionArchive.java @@ -21,9 +21,19 @@ package org.olat.ims.qti21.manager.archive.interactions; import java.util.List; +import org.olat.core.util.StringHelper; +import org.olat.core.util.openxml.OpenXMLWorkbook; +import org.olat.core.util.openxml.OpenXMLWorksheet.Row; +import org.olat.ims.qti21.AssessmentResponse; +import org.olat.ims.qti21.manager.CorrectResponsesUtil; + +import uk.ac.ed.ph.jqtiplus.node.content.basic.TextRun; +import uk.ac.ed.ph.jqtiplus.node.content.variable.TextOrVariable; +import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem; import uk.ac.ed.ph.jqtiplus.node.item.interaction.InlineChoiceInteraction; import uk.ac.ed.ph.jqtiplus.node.item.interaction.Interaction; -import uk.ac.ed.ph.jqtiplus.node.item.interaction.choice.Choice; +import uk.ac.ed.ph.jqtiplus.node.item.interaction.choice.InlineChoice; +import uk.ac.ed.ph.jqtiplus.types.Identifier; /** * @@ -31,12 +41,69 @@ import uk.ac.ed.ph.jqtiplus.node.item.interaction.choice.Choice; * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ -public class InlineChoiceInteractionArchive extends AbstractChoiceInteractionArchive { +public class InlineChoiceInteractionArchive extends DefaultInteractionArchive { + + @Override + public int writeHeader1(AssessmentItem item, Interaction interaction, int itemNumber, int interactionNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { + if(interactionNumber == 0) { + String header = item.getTitle(); + dataRow.addCell(col, header, workbook.getStyles().getHeaderStyle()); + } + + col += 1; + return col; + } @Override - public List<? extends Choice> getChoices(Interaction interaction) { + public int writeHeader2(AssessmentItem item, Interaction interaction, int itemNumber, int interactionNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { + String header = (itemNumber + 1) + "_IC1"; + dataRow.addCell(col++, header, workbook.getStyles().getHeaderStyle()); + return col; + } + + @Override + public int writeInteractionData(AssessmentItem item, AssessmentResponse response, Interaction interaction, int itemNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { InlineChoiceInteraction choiceInteraction = (InlineChoiceInteraction)interaction; - return choiceInteraction.getInlineChoices(); + + List<Identifier> correctAnswers = CorrectResponsesUtil.getCorrectIdentifierResponses(item, interaction); + String stringuifiedResponse = response == null ? null : response.getStringuifiedResponse(); + if(StringHelper.containsNonWhitespace(stringuifiedResponse)) { + String inlineResponse = CorrectResponsesUtil.stripResponse(stringuifiedResponse); + Identifier responseIdentifier = Identifier.assumedLegal(inlineResponse); + + boolean correct = false; + for(Identifier correctAnswer:correctAnswers) { + if(responseIdentifier.equals(correctAnswer)) { + correct = true; + break; + } + } + + InlineChoice selectedChoice = choiceInteraction.getInlineChoice(responseIdentifier); + if(selectedChoice != null) { + String value = getTextContent(selectedChoice); + if(correct) { + dataRow.addCell(col++, value, workbook.getStyles().getCorrectStyle()); + } else { + dataRow.addCell(col++, value); + } + } else { + col++; + } + } else { + col++; + } + return col; + } + + private String getTextContent(InlineChoice selectedChoice) { + List<TextOrVariable> values = selectedChoice.getTextOrVariables(); + for(TextOrVariable value:values) { + if(value instanceof TextRun) { + return ((TextRun)value).getTextContent(); + } + } + return selectedChoice.getIdentifier().toString(); } } diff --git a/src/main/java/org/olat/ims/qti21/manager/archive/interactions/InteractionArchive.java b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/InteractionArchive.java index ce3e9784852c0d265a28742f732edecf5baacadf..78ad2c66cd1ac8190a60ac03bb3b4180877b89e9 100644 --- a/src/main/java/org/olat/ims/qti21/manager/archive/interactions/InteractionArchive.java +++ b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/InteractionArchive.java @@ -46,7 +46,7 @@ public interface InteractionArchive { */ public int writeHeader1(AssessmentItem item, Interaction interaction, int itemNumber, int interactionNumber, Row dataRow, int col, OpenXMLWorkbook workbook); - public int writeHeader2(Interaction interaction, int itemNumber, int interactionNumber, Row dataRow, int col, OpenXMLWorkbook workbook); + public int writeHeader2(AssessmentItem item, Interaction interaction, int itemNumber, int interactionNumber, Row dataRow, int col, OpenXMLWorkbook workbook); public int writeInteractionData(AssessmentItem item, AssessmentResponse response, Interaction interaction, int itemNumber, Row dataRow, int col, OpenXMLWorkbook workbook); diff --git a/src/main/java/org/olat/ims/qti21/manager/archive/interactions/MatchInteractionArchive.java b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/MatchInteractionArchive.java index f157a9a23f5f253910be18aacb2505299239fc7a..9c45638a224bd2f77c378f7ad48ee044f89b758c 100644 --- a/src/main/java/org/olat/ims/qti21/manager/archive/interactions/MatchInteractionArchive.java +++ b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/MatchInteractionArchive.java @@ -19,6 +19,7 @@ */ package org.olat.ims.qti21.manager.archive.interactions; +import java.util.List; import java.util.Set; import org.olat.core.util.StringHelper; @@ -26,6 +27,7 @@ import org.olat.core.util.openxml.OpenXMLWorkbook; import org.olat.core.util.openxml.OpenXMLWorksheet.Row; import org.olat.ims.qti21.AssessmentResponse; import org.olat.ims.qti21.manager.CorrectResponsesUtil; +import org.olat.ims.qti21.manager.archive.SimpleContentRenderer; import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem; import uk.ac.ed.ph.jqtiplus.node.item.interaction.Interaction; @@ -49,19 +51,27 @@ public class MatchInteractionArchive extends DefaultInteractionArchive { } MatchInteraction matchInteraction = (MatchInteraction)interaction; int numOfChoices = matchInteraction.getSimpleMatchSets().get(0).getSimpleAssociableChoices().size(); - if(interaction.getResponseIdentifier().toString().startsWith("KPRIM_")) { + if(numOfChoices > 0) { col += (numOfChoices - 1); } - return col++; + return col; } @Override - public int writeHeader2(Interaction interaction, int itemNumber, int interactionNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { + public int writeHeader2(AssessmentItem item, Interaction interaction, int itemNumber, int interactionNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { MatchInteraction matchInteraction = (MatchInteraction)interaction; + + boolean kprim = matchInteraction.getResponseIdentifier().toString().startsWith("KPRIM_"); + String fix = kprim ? "_KP" : "_K"; + int numOfChoices = matchInteraction.getSimpleMatchSets().get(0).getSimpleAssociableChoices().size(); - for(int i=0; i<numOfChoices; i++) { - String header = (itemNumber + 1) + "_K" + i; - dataRow.addCell(col++, header, workbook.getStyles().getHeaderStyle()); + if(numOfChoices > 0) { + for(int i=0; i<numOfChoices; i++) { + String header = (itemNumber + 1) + fix + (i + 1); + dataRow.addCell(col++, header, workbook.getStyles().getHeaderStyle()); + } + } else { + col++; } return col; } @@ -73,36 +83,78 @@ public class MatchInteractionArchive extends DefaultInteractionArchive { String stringuifiedResponse = response == null ? null : response.getStringuifiedResponse(); if(!StringHelper.containsNonWhitespace(stringuifiedResponse)) { col += matchInteraction.getSimpleMatchSets().get(0).getSimpleAssociableChoices().size(); - } else if(matchInteraction.getResponseIdentifier().toString().startsWith("KPRIM_")) { - Set<String> rightResponses = CorrectResponsesUtil.getCorrectKPrimResponses(item, matchInteraction); - SimpleMatchSet fourMatchSet = matchInteraction.getSimpleMatchSets().get(0); - for(SimpleAssociableChoice choice:fourMatchSet.getSimpleAssociableChoices()) { + } else { + boolean kprim = matchInteraction.getResponseIdentifier().toString().startsWith("KPRIM_"); + + Set<String> correctAnswers = CorrectResponsesUtil.getCorrectDirectPairResponses(item, matchInteraction, false); + List<String> responses = CorrectResponsesUtil.parseResponses(stringuifiedResponse); + + SimpleMatchSet firstMatchSet = matchInteraction.getSimpleMatchSets().get(0); + SimpleMatchSet secondMatchSet = matchInteraction.getSimpleMatchSets().get(1); + + for(SimpleAssociableChoice choice:firstMatchSet.getSimpleAssociableChoices()) { String choiceIdentifier = choice.getIdentifier().toString(); - String markerCorrect = "[" + choiceIdentifier + " correct]"; - String markerWrong = "[" + choiceIdentifier + " wrong]"; - - boolean isCorrectRight = rightResponses.contains(markerCorrect); - String rightFlag = isCorrectRight ? markerCorrect : markerWrong; - String wrongFlag = isCorrectRight ? markerWrong : markerCorrect; - String value = null; - if(stringuifiedResponse.contains(markerCorrect)) { - value = "+"; - } else if(stringuifiedResponse.contains(markerWrong)) { - value = "-"; - } - - if(stringuifiedResponse.indexOf(rightFlag) >= 0) { - dataRow.addCell(col++, value, workbook.getStyles().getCorrectStyle()); - } else if(stringuifiedResponse.indexOf(wrongFlag) >= 0) { - dataRow.addCell(col++, value, null); + if(kprim) { + String markerCorrect = choiceIdentifier + " correct"; + String markerWrong = choiceIdentifier + " wrong"; + + boolean isCorrectRight = correctAnswers.contains(markerCorrect); + String rightFlag = isCorrectRight ? markerCorrect : markerWrong; + String wrongFlag = isCorrectRight ? markerWrong : markerCorrect; + + String value = null; + if(stringuifiedResponse.contains(markerCorrect)) { + value = "+"; + } else if(stringuifiedResponse.contains(markerWrong)) { + value = "-"; + } + + if(stringuifiedResponse.indexOf(rightFlag) >= 0) { + dataRow.addCell(col++, value, workbook.getStyles().getCorrectStyle()); + } else if(stringuifiedResponse.indexOf(wrongFlag) >= 0) { + dataRow.addCell(col++, value, null); + } else { + col++; + } } else { - col++; + String aResponse = null; + for(String r:responses) { + if(r.startsWith(choiceIdentifier)) { + aResponse = r; + } + } + + if(StringHelper.containsNonWhitespace(aResponse)) { + boolean correct = correctAnswers.contains(aResponse); + + String value = null; + for(SimpleAssociableChoice association:secondMatchSet.getSimpleAssociableChoices()) { + if(aResponse.endsWith(association.getIdentifier().toString())) { + value = getContent(association); + break; + } + } + + if(correct) { + dataRow.addCell(col++, value, workbook.getStyles().getCorrectStyle()); + } else { + dataRow.addCell(col++, value, null); + } + } else { + col++; + } } } - } else { - col += matchInteraction.getSimpleMatchSets().get(0).getSimpleAssociableChoices().size(); } return col; } + + private String getContent(SimpleAssociableChoice association) { + String content = SimpleContentRenderer.renderFlowStatics(association.getFlowStatics()); + if(StringHelper.containsNonWhitespace(content)) { + return content; + } + return association.getIdentifier().toString(); + } } diff --git a/src/main/java/org/olat/ims/qti21/manager/archive/interactions/NoOutputInteractionArchive.java b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/NoOutputInteractionArchive.java new file mode 100644 index 0000000000000000000000000000000000000000..c70a2eeb6b5cd34421b8c45707f21b887847f45e --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/NoOutputInteractionArchive.java @@ -0,0 +1,53 @@ +/** + * <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.archive.interactions; + +import org.olat.core.util.openxml.OpenXMLWorkbook; +import org.olat.core.util.openxml.OpenXMLWorksheet.Row; +import org.olat.ims.qti21.AssessmentResponse; + +import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem; +import uk.ac.ed.ph.jqtiplus.node.item.interaction.Interaction; + +/** + * + * Doesn't make any cells + * + * Initial date: 27.04.2016<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class NoOutputInteractionArchive implements InteractionArchive { + + @Override + public int writeHeader1(AssessmentItem item, Interaction interaction, int itemNumber, int interactionNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { + return col; + } + + @Override + public int writeHeader2(AssessmentItem item, Interaction interaction, int itemNumber, int interactionNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { + return col; + } + + @Override + public int writeInteractionData(AssessmentItem item, AssessmentResponse response, Interaction interaction, int itemNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { + return col; + } +} diff --git a/src/main/java/org/olat/ims/qti21/manager/archive/interactions/OrderInteractionArchive.java b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/OrderInteractionArchive.java index 0d19e6a5dff94e7fb437382c3a46dc52aaa1c957..ebfae530e98f90c0c67d4d3ae6312432d5f7962d 100644 --- a/src/main/java/org/olat/ims/qti21/manager/archive/interactions/OrderInteractionArchive.java +++ b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/OrderInteractionArchive.java @@ -19,6 +19,21 @@ */ package org.olat.ims.qti21.manager.archive.interactions; +import java.util.List; + +import org.olat.core.util.StringHelper; +import org.olat.core.util.openxml.OpenXMLWorkbook; +import org.olat.core.util.openxml.OpenXMLWorksheet.Row; +import org.olat.ims.qti21.AssessmentResponse; +import org.olat.ims.qti21.manager.CorrectResponsesUtil; +import org.olat.ims.qti21.manager.archive.SimpleContentRenderer; + +import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem; +import uk.ac.ed.ph.jqtiplus.node.item.interaction.Interaction; +import uk.ac.ed.ph.jqtiplus.node.item.interaction.OrderInteraction; +import uk.ac.ed.ph.jqtiplus.node.item.interaction.choice.SimpleChoice; +import uk.ac.ed.ph.jqtiplus.types.Identifier; + /** * * Initial date: 27.04.2016<br> @@ -27,4 +42,78 @@ package org.olat.ims.qti21.manager.archive.interactions; */ public class OrderInteractionArchive extends DefaultInteractionArchive { + @Override + public int writeHeader1(AssessmentItem item, Interaction interaction, int itemNumber, int interactionNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { + if(interactionNumber == 0) { + String header = item.getTitle(); + dataRow.addCell(col, header, workbook.getStyles().getHeaderStyle()); + } + + OrderInteraction orderInteraction = (OrderInteraction)interaction; + int numOfChoices = orderInteraction.getSimpleChoices().size(); + col += Math.max(1, numOfChoices); + return col; + } + + @Override + public int writeHeader2(AssessmentItem item, Interaction interaction, int itemNumber, int interactionNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { + OrderInteraction orderInteraction = (OrderInteraction)interaction; + int numOfChoices = orderInteraction.getSimpleChoices().size(); + if(numOfChoices > 0) { + for(int i=0; i<numOfChoices; i++) { + String header = (itemNumber + 1) + "_O" + (i + 1); + dataRow.addCell(col++, header, workbook.getStyles().getHeaderStyle()); + } + } else { + col++; + } + return col; + } + + @Override + public int writeInteractionData(AssessmentItem item, AssessmentResponse response, Interaction interaction, + int itemNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { + OrderInteraction orderInteraction = (OrderInteraction)interaction; + List<SimpleChoice> choices = orderInteraction.getSimpleChoices(); + + if(choices.size() > 0) { + String stringuifiedResponse = response == null ? null : response.getStringuifiedResponse(); + List<String> responses = CorrectResponsesUtil.parseResponses(stringuifiedResponse); + List<Identifier> correctAnswers = CorrectResponsesUtil.getCorrectOrderedIdentifierResponses(item, interaction); + + for(int i=0; i<choices.size(); i++) { + String currentResponse = null; + String currentResponseText = null; + if(responses.size() > i) { + currentResponse = currentResponseText = responses.get(i); + SimpleChoice selectedChoice = orderInteraction.getSimpleChoice(Identifier.assumedLegal(currentResponse)); + if(selectedChoice != null) { + currentResponseText = getContent(selectedChoice); + } + } + + String correctAnswer = null; + if(correctAnswers.size() > i) { + correctAnswer = correctAnswers.get(i).toString(); + } + + if(correctAnswer != null && correctAnswer.equals(currentResponse)) { + dataRow.addCell(col++, currentResponseText, workbook.getStyles().getCorrectStyle()); + } else { + dataRow.addCell(col++, currentResponseText, null); + } + } + } else { + col++; + } + return col; + } + + private String getContent(SimpleChoice choice) { + String content = SimpleContentRenderer.renderFlowStatics(choice.getFlowStatics()); + if(StringHelper.containsNonWhitespace(content)) { + return content; + } + return choice.getIdentifier().toString(); + } } diff --git a/src/main/java/org/olat/ims/qti21/manager/archive/interactions/PositionObjectInteractionArchive.java b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/PositionObjectInteractionArchive.java index 1cf8106e620e42937334441a450a57d0dd082a32..4fe2e3982768ac64d12315af91fc17351dab9c3d 100644 --- a/src/main/java/org/olat/ims/qti21/manager/archive/interactions/PositionObjectInteractionArchive.java +++ b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/PositionObjectInteractionArchive.java @@ -39,6 +39,8 @@ public class PositionObjectInteractionArchive extends DefaultInteractionArchive public int writeInteractionData(AssessmentItem item, AssessmentResponse response, Interaction interaction, int itemNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { PositionObjectInteraction positionInteraction = (PositionObjectInteraction)interaction; + + return super.writeInteractionData(item, response, positionInteraction, itemNumber, dataRow, col, workbook); } diff --git a/src/main/java/org/olat/ims/qti21/manager/archive/interactions/SelectPointInteractionArchive.java b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/SelectPointInteractionArchive.java index 6350d28eb21731aa21207eae2047c8d2a252fb93..17d4b888a7de14d9c969a85911640db656bb206f 100644 --- a/src/main/java/org/olat/ims/qti21/manager/archive/interactions/SelectPointInteractionArchive.java +++ b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/SelectPointInteractionArchive.java @@ -19,13 +19,21 @@ */ package org.olat.ims.qti21.manager.archive.interactions; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + import org.olat.core.util.openxml.OpenXMLWorkbook; import org.olat.core.util.openxml.OpenXMLWorksheet.Row; import org.olat.ims.qti21.AssessmentResponse; +import org.olat.ims.qti21.manager.CorrectResponsesUtil; import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem; import uk.ac.ed.ph.jqtiplus.node.item.interaction.Interaction; -import uk.ac.ed.ph.jqtiplus.node.item.interaction.SelectPointInteraction; +import uk.ac.ed.ph.jqtiplus.node.item.response.declaration.AreaMapEntry; +import uk.ac.ed.ph.jqtiplus.node.item.response.declaration.AreaMapping; +import uk.ac.ed.ph.jqtiplus.node.item.response.declaration.ResponseDeclaration; +import uk.ac.ed.ph.jqtiplus.value.PointValue; /** * @@ -36,11 +44,83 @@ import uk.ac.ed.ph.jqtiplus.node.item.interaction.SelectPointInteraction; public class SelectPointInteractionArchive extends DefaultInteractionArchive { @Override - public int writeInteractionData(AssessmentItem item, AssessmentResponse response, Interaction interaction, - int itemNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { - SelectPointInteraction selectInteraction = (SelectPointInteraction)interaction; - return super.writeInteractionData(item, response, selectInteraction, itemNumber, dataRow, col, workbook); + public int writeHeader1(AssessmentItem item, Interaction interaction, int itemNumber, int interactionNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { + if(interactionNumber == 0) { + String header = item.getTitle(); + dataRow.addCell(col++, header, workbook.getStyles().getHeaderStyle()); + } else { + col++; + } + List<AreaMapEntry> areaMapEntries = getAreaMapEntries(item, interaction); + if(areaMapEntries.size() > 0) { + col += (areaMapEntries.size() - 1); + } + return col; + } + + @Override + public int writeHeader2(AssessmentItem item, Interaction interaction, int itemNumber, int interactionNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { + List<AreaMapEntry> areaMapEntries = getAreaMapEntries(item, interaction); + if(areaMapEntries.size() == 0) { + col++; + } else { + for(AreaMapEntry areaMapEntry:areaMapEntries) { + dataRow.addCell(col++, areaMapEntry.getShape().toQtiString()); + } + } + return col; } - + @Override + public int writeInteractionData(AssessmentItem item, AssessmentResponse response, Interaction interaction, int itemNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { + List<AreaMapEntry> areaMapEntries = getAreaMapEntries(item, interaction); + if(areaMapEntries.size() == 0) { + col++; + } else { + String stringuifiedResponse = response == null ? null : response.getStringuifiedResponse(); + List<String> responses = CorrectResponsesUtil.parseResponses(stringuifiedResponse); + List<PointValue> responsePoints = CorrectResponsesUtil.parseResponses(responses); + + List<Integer> freeSlots = new ArrayList<>(); + for(AreaMapEntry areaMapEntry:areaMapEntries) { + int[] coords = CorrectResponsesUtil.convertCoordinates(areaMapEntry.getCoordinates()); + + PointValue foundPoints = null; + for(PointValue responsePoint:responsePoints) { + if(areaMapEntry.getShape().isInside(coords, responsePoint)) { + foundPoints = responsePoint; + responsePoints.remove(responsePoint); + break; + } + } + + if(foundPoints != null) { + dataRow.addCell(col++, foundPoints.toQtiString(), workbook.getStyles().getCorrectStyle()); + } else { + freeSlots.add(new Integer(col)); + dataRow.addCell(col++, "", null); + } + } + + //fill the rest + for(PointValue responsePoint: responsePoints) { + if(freeSlots.size() > 0) { + Integer slot = freeSlots.get(0); + dataRow.addCell(slot, responsePoint.toQtiString(), null); + } + } + } + return col++; + } + + private List<AreaMapEntry> getAreaMapEntries(AssessmentItem item, Interaction interaction) { + ResponseDeclaration responseDeclaration = item.getResponseDeclaration(interaction.getResponseIdentifier()); + if(responseDeclaration != null && responseDeclaration.getAreaMapping() != null) { + AreaMapping mapping = responseDeclaration.getAreaMapping(); + if(mapping != null) { + return mapping.getAreaMapEntries(); + } + } + return Collections.emptyList(); + } } diff --git a/src/main/java/org/olat/ims/qti21/manager/archive/interactions/SliderInteractionArchive.java b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/SliderInteractionArchive.java index ca09af9bda61b117a36099d02b675d28d877f504..2d356f1f3473a0f064f5e50ca7173219f799eb50 100644 --- a/src/main/java/org/olat/ims/qti21/manager/archive/interactions/SliderInteractionArchive.java +++ b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/SliderInteractionArchive.java @@ -19,6 +19,17 @@ */ package org.olat.ims.qti21.manager.archive.interactions; +import java.util.List; + +import org.olat.core.util.StringHelper; +import org.olat.core.util.openxml.OpenXMLWorkbook; +import org.olat.core.util.openxml.OpenXMLWorksheet.Row; +import org.olat.ims.qti21.AssessmentResponse; +import org.olat.ims.qti21.manager.CorrectResponsesUtil; + +import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem; +import uk.ac.ed.ph.jqtiplus.node.item.interaction.Interaction; + /** * * Initial date: 27.04.2016<br> @@ -26,5 +37,48 @@ package org.olat.ims.qti21.manager.archive.interactions; * */ public class SliderInteractionArchive extends DefaultInteractionArchive { + + @Override + public int writeHeader1(AssessmentItem item, Interaction interaction, int itemNumber, int interactionNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { + if(interactionNumber == 0) { + String header = item.getTitle(); + dataRow.addCell(col, header, workbook.getStyles().getHeaderStyle()); + } + col += 1; + return col; + } + + @Override + public int writeHeader2(AssessmentItem item, Interaction interaction, int itemNumber, int interactionNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { + String header = (itemNumber + 1) + "_SL1"; + dataRow.addCell(col++, header, workbook.getStyles().getHeaderStyle()); + return col; + } + + @Override + public int writeInteractionData(AssessmentItem item, AssessmentResponse response, Interaction interaction, + int itemNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { + + List<Integer> correctAnswers = CorrectResponsesUtil.getCorrectIntegerResponses(item, interaction); + String stringuifiedResponse = response == null ? null : response.getStringuifiedResponse(); + Integer selectedResponse = null; + if(StringHelper.containsNonWhitespace(stringuifiedResponse)) { + try { + selectedResponse = Integer.parseInt(CorrectResponsesUtil.stripResponse(stringuifiedResponse)); + } catch (NumberFormatException e) { + //parsing can be a problem + } + } + + if(selectedResponse == null) { + col++; + } else if(correctAnswers.contains(selectedResponse)) { + dataRow.addCell(col++, selectedResponse, workbook.getStyles().getCorrectStyle()); + } else { + dataRow.addCell(col++, selectedResponse, null); + } + return col; + } } + diff --git a/src/main/java/org/olat/ims/qti21/manager/archive/interactions/TextEntryInteractionArchive.java b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/TextEntryInteractionArchive.java index b263a968716c71d276b540f23107291e84b57156..b602bae321fa7cea34bd7957fa32c760d9dc3225 100644 --- a/src/main/java/org/olat/ims/qti21/manager/archive/interactions/TextEntryInteractionArchive.java +++ b/src/main/java/org/olat/ims/qti21/manager/archive/interactions/TextEntryInteractionArchive.java @@ -39,7 +39,7 @@ import uk.ac.ed.ph.jqtiplus.node.item.interaction.TextEntryInteraction; public class TextEntryInteractionArchive extends DefaultInteractionArchive { @Override - public int writeHeader2(Interaction interaction, int itemNumber, int interactionNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { + public int writeHeader2(AssessmentItem item, Interaction interaction, int itemNumber, int interactionNumber, Row dataRow, int col, OpenXMLWorkbook workbook) { String header = (itemNumber + 1) + "_FIB" + (interactionNumber + 1); dataRow.addCell(col++, header, workbook.getStyles().getHeaderStyle()); return col; diff --git a/src/test/java/org/olat/ims/qti21/manager/CorrectResponsesUtilTest.java b/src/test/java/org/olat/ims/qti21/manager/CorrectResponsesUtilTest.java new file mode 100644 index 0000000000000000000000000000000000000000..f046f79c27d03a1b1d6df26556ea5fb24b5c607c --- /dev/null +++ b/src/test/java/org/olat/ims/qti21/manager/CorrectResponsesUtilTest.java @@ -0,0 +1,47 @@ +/** + * <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.util.List; + +import org.junit.Assert; +import org.junit.Test; + +/** + * + * Initial date: 27.04.2016<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class CorrectResponsesUtilTest { + + @Test + public void parseResponses() { + String stringuifiedResponses = "[20 30][test][]"; + List<String> responses = CorrectResponsesUtil.parseResponses(stringuifiedResponses); + + Assert.assertNotNull(responses); + Assert.assertEquals(3, responses.size()); + Assert.assertEquals("20 30", responses.get(0)); + Assert.assertEquals("test", responses.get(1)); + Assert.assertEquals("", responses.get(2)); + } + +} diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java index f10df1d776d33406d5208022316e0f642e510b57..90dc7b66a5a7729fb3444817d0bafcee87e5b65b 100644 --- a/src/test/java/org/olat/test/AllTestsJunit4.java +++ b/src/test/java/org/olat/test/AllTestsJunit4.java @@ -176,6 +176,7 @@ import org.junit.runners.Suite; org.olat.ims.qti.statistics.manager.StatisticsTest.class, org.olat.ims.qti21.manager.AssessmentTestSessionDAOTest.class, org.olat.ims.qti21.manager.AssessmentResponseDAOTest.class, + org.olat.ims.qti21.manager.CorrectResponsesUtilTest.class, org.olat.ims.lti.LTIManagerTest.class, org.olat.modules.webFeed.FeedManagerImplTest.class, org.olat.modules.qpool.manager.MetadataConverterHelperTest.class,