From 50bcc77aa84163576126efa806a795b8ff99b8d0 Mon Sep 17 00:00:00 2001 From: srosse <none@none> Date: Fri, 23 Feb 2018 12:32:28 +0100 Subject: [PATCH] OO-3338: add section with their score in the Excel export for QTI 2.1 --- .../util/openxml/OpenXMLWorkbookStyles.java | 11 +- .../manager/archive/QTI21ArchiveFormat.java | 270 ++++++++++++------ .../qti21/ui/_i18n/LocalStrings_de.properties | 1 + .../qti21/ui/_i18n/LocalStrings_en.properties | 1 + .../qti21/ui/_i18n/LocalStrings_fr.properties | 1 + 5 files changed, 196 insertions(+), 88 deletions(-) diff --git a/src/main/java/org/olat/core/util/openxml/OpenXMLWorkbookStyles.java b/src/main/java/org/olat/core/util/openxml/OpenXMLWorkbookStyles.java index 4885c5906b5..6e9b226dcc7 100644 --- a/src/main/java/org/olat/core/util/openxml/OpenXMLWorkbookStyles.java +++ b/src/main/java/org/olat/core/util/openxml/OpenXMLWorkbookStyles.java @@ -48,7 +48,7 @@ public class OpenXMLWorkbookStyles { private List<CellStyle> cellXfs = new ArrayList<>(); private final Font standardFont, boldFont; - private final Fill noneFile, gray125Fill, correctFill; + private final Fill noneFile, gray125Fill, lightGrayFill, correctFill; private final Border noBorder, borderRight; /** @@ -62,6 +62,7 @@ public class OpenXMLWorkbookStyles { private final CellStyle headerStyle; private final CellStyle correctStyle; private final CellStyle percentStyle; + private final CellStyle lightGrayStyle; public OpenXMLWorkbookStyles() { standardFont = new Font(fonts.size(), "12", "1", "Calibri", "2", "minor", FontStyle.none); @@ -73,6 +74,8 @@ public class OpenXMLWorkbookStyles { fills.add(noneFile); gray125Fill = new Fill(fills.size(), "gray125"); fills.add(gray125Fill); + lightGrayFill = new Fill(fills.size(), "solid", "EFEFEFEF", "64"); + fills.add(lightGrayFill); correctFill = new Fill(fills.size(), "solid", "FFC3FFC0", "64"); fills.add(correctFill); @@ -97,6 +100,8 @@ public class OpenXMLWorkbookStyles { cellXfs.add(correctStyle); percentStyle = new CellStyle(cellXfs.size(), PERCENT_FORMAT, standardFont, noneFile, borderRight, null, "1"); cellXfs.add(percentStyle); + lightGrayStyle = new CellStyle(cellXfs.size(), "0", standardFont, lightGrayFill, borderRight, null, null); + cellXfs.add(lightGrayStyle); } public CellStyle getBorderRightStyle() { @@ -135,6 +140,10 @@ public class OpenXMLWorkbookStyles { return percentStyle; } + public CellStyle getLightGrayStyle() { + return lightGrayStyle; + } + public List<Font> getFonts() { return fonts; } 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 ea3f5c7ce2a..b440cbe9603 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 @@ -22,6 +22,7 @@ package org.olat.ims.qti21.manager.archive; import java.io.File; import java.io.IOException; import java.io.OutputStream; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collections; import java.util.Date; @@ -117,6 +118,10 @@ import uk.ac.ed.ph.jqtiplus.node.item.interaction.SliderInteraction; import uk.ac.ed.ph.jqtiplus.node.item.interaction.TextEntryInteraction; import uk.ac.ed.ph.jqtiplus.node.item.interaction.UploadInteraction; 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.SectionPart; +import uk.ac.ed.ph.jqtiplus.node.test.TestPart; import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentItem; import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentTest; import uk.ac.ed.ph.jqtiplus.types.Identifier; @@ -140,8 +145,9 @@ public class QTI21ArchiveFormat { private final QTI21StatisticSearchParams searchParams; private ExportFormat exportConfig; + private int numOfSections; private CourseNode courseNode; - private List<ItemInfos> itemInfos; + private List<AbstractInfos> elementInfos; private final Map<String, InteractionArchive> interactionArchiveMap = new HashMap<>(); private final QTI21Service qtiService; @@ -332,30 +338,39 @@ public class QTI21ArchiveFormat { header1Row.addCell(col++, translator.translate("archive.table.header.test"), headerStyle); col += 5; - List<ItemInfos> infos = getItemInfos(); + List<AbstractInfos> infos = getItemInfos(); for(int i=0; i<infos.size(); i++) { int delta = col; - ItemInfos item = infos.get(i); - if (exportConfig.isResponseCols() || exportConfig.isPointCol() || exportConfig.isTimeCols() || exportConfig.isCommentCol()) { - 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.getAssessmentItem(), interaction, i, j, header1Row, col, workbook); + AbstractInfos info = infos.get(i); + if(info instanceof ItemInfos) { + ItemInfos item = (ItemInfos)info; + if (exportConfig.isResponseCols() || exportConfig.isPointCol() || exportConfig.isTimeCols() || exportConfig.isCommentCol()) { + 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.getAssessmentItem(), interaction, i, j, header1Row, col, workbook); + } + } + if (!exportConfig.isResponseCols()) { + col -= col - delta; + } + if (exportConfig.isPointCol()) { + col++; + } + if (exportConfig.isCommentCol()) { + col++; + } + if (exportConfig.isTimeCols()) { + col += anonymizerCallback != null ? 1 : 2; + } + } else if(numOfSections > 1 && info instanceof SectionInfos) { + SectionInfos section = (SectionInfos)info; + if(!section.getItemInfos().isEmpty()) { + String sectionTitle = translator.translate("archive.table.header.section", new String[] { section.getAssessmentSection().getTitle() }); + header1Row.addCell(col++, sectionTitle, headerStyle); } } - if (!exportConfig.isResponseCols()) { - col -= col - delta; - } - if (exportConfig.isPointCol()) { - col++; - } - if (exportConfig.isCommentCol()) { - col++; - } - if (exportConfig.isTimeCols()) { - col += anonymizerCallback != null ? 1 : 2; - } } } @@ -402,28 +417,36 @@ public class QTI21ArchiveFormat { } header2Row.addCell(col++, translator.translate("column.header.duration"), headerStyle); - List<ItemInfos> infos = getItemInfos(); + List<AbstractInfos> infos = getItemInfos(); for(int i=0; i<infos.size(); i++) { - ItemInfos info = infos.get(i); - if (exportConfig.isResponseCols()) { - List<Interaction> interactions = info.getInteractions(); - for(int j=0; j<interactions.size(); j++) { - Interaction interaction = interactions.get(j); - col = interactionArchiveMap.get(interaction.getQtiClassName()) - .writeHeader2(info.getAssessmentItem(), interaction, i, j, header2Row, col, workbook); + AbstractInfos info = infos.get(i); + if(info instanceof ItemInfos) { + ItemInfos item = (ItemInfos)info; + if (exportConfig.isResponseCols()) { + List<Interaction> interactions = item.getInteractions(); + for(int j=0; j<interactions.size(); j++) { + Interaction interaction = interactions.get(j); + col = interactionArchiveMap.get(interaction.getQtiClassName()) + .writeHeader2(item.getAssessmentItem(), interaction, i, j, header2Row, col, workbook); + } } - } - if (exportConfig.isPointCol()) { - header2Row.addCell(col++, translator.translate("item.score"), headerStyle); - } - if (exportConfig.isCommentCol()) { - header2Row.addCell(col++, translator.translate("item.comment"), headerStyle); - } - if (exportConfig.isTimeCols()) { - if (anonymizerCallback == null){ - header2Row.addCell(col++, translator.translate("item.start"), headerStyle); + if (exportConfig.isPointCol()) { + header2Row.addCell(col++, translator.translate("item.score"), headerStyle); + } + if (exportConfig.isCommentCol()) { + header2Row.addCell(col++, translator.translate("item.comment"), headerStyle); + } + if (exportConfig.isTimeCols()) { + if (anonymizerCallback == null){ + header2Row.addCell(col++, translator.translate("item.start"), headerStyle); + } + header2Row.addCell(col++, translator.translate("item.duration"), headerStyle); + } + } else if(numOfSections > 1 && info instanceof SectionInfos) { + SectionInfos section = (SectionInfos)info; + if(!section.getItemInfos().isEmpty()) { + header2Row.addCell(col++, translator.translate("archive.table.header.points"), headerStyle); } - header2Row.addCell(col++, translator.translate("item.duration"), headerStyle); } } } @@ -552,74 +575,126 @@ public class QTI21ArchiveFormat { } dataRow.addCell(col++, toDurationInMilliseconds(testSession.getDuration()), null); - List<ItemInfos> infos = getItemInfos(); + List<AbstractInfos> 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); - - if (exportConfig.isResponseCols()) { - 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(info.getAssessmentItem(), response, interaction, j, dataRow, col, workbook); + AbstractInfos info = infos.get(i); + if(info instanceof ItemInfos) { + ItemInfos item = (ItemInfos)info; + AssessmentItemRef itemRef = item.getAssessmentItemRef(); + String itemRefIdentifier = itemRef.getIdentifier().toString(); + AssessmentItemSession itemSession = responses.getItemSession(itemRefIdentifier); + + if (exportConfig.isResponseCols()) { + List<Interaction> interactions = item.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.getAssessmentItem(), response, interaction, j, dataRow, col, workbook); + } } - } - //score, start, duration - if (itemSession == null) { - if (exportConfig.isPointCol()) { - col++; - } - if (exportConfig.isCommentCol()) { - col++; - } - if (exportConfig.isTimeCols()) { - col += anonymizerCallback != null ? 1 : 2; - } - } else { - if (exportConfig.isPointCol()) { - if(itemSession.getManualScore() != null) { - dataRow.addCell(col++, itemSession.getManualScore(), null); - } else { - dataRow.addCell(col++, itemSession.getScore(), null); + //score, start, duration + if (itemSession == null) { + if (exportConfig.isPointCol()) { + col++; + } + if (exportConfig.isCommentCol()) { + col++; + } + if (exportConfig.isTimeCols()) { + col += anonymizerCallback != null ? 1 : 2; + } + } else { + if (exportConfig.isPointCol()) { + if(itemSession.getManualScore() != null) { + dataRow.addCell(col++, itemSession.getManualScore(), null); + } else { + dataRow.addCell(col++, itemSession.getScore(), null); + } + } + if (exportConfig.isCommentCol()) { + dataRow.addCell(col++, itemSession.getCoachComment(), null); + } + if (exportConfig.isTimeCols()) { + if (anonymizerCallback == null){ + dataRow.addCell(col++, itemSession.getCreationDate(), workbook.getStyles().getTimeStyle()); + } + dataRow.addCell(col++, toDurationInMilliseconds(itemSession.getDuration()), null); } } - if (exportConfig.isCommentCol()) { - dataRow.addCell(col++, itemSession.getCoachComment(), null); - } - if (exportConfig.isTimeCols()) { - if (anonymizerCallback == null){ - dataRow.addCell(col++, itemSession.getCreationDate(), workbook.getStyles().getTimeStyle()); + } else if(numOfSections > 1 && info instanceof SectionInfos) { + SectionInfos section = (SectionInfos)info; + if(!section.getItemInfos().isEmpty()) { + BigDecimal score = calculateSectionScore(responses, section); + if(score != null) { + dataRow.addCell(col++, score, workbook.getStyles().getLightGrayStyle()); + } else { + col++; } - dataRow.addCell(col++, toDurationInMilliseconds(itemSession.getDuration()), null); } } } } + private BigDecimal calculateSectionScore(SessionResponses responses, SectionInfos section) { + BigDecimal sectionScore = BigDecimal.valueOf(0l); + + for(ItemInfos item:section.getItemInfos()) { + AssessmentItemRef itemRef = item.getAssessmentItemRef(); + String itemRefIdentifier = itemRef.getIdentifier().toString(); + AssessmentItemSession itemSession = responses.getItemSession(itemRefIdentifier); + if(itemSession.getManualScore() != null) { + sectionScore = sectionScore.add(itemSession.getManualScore()); + } else if(itemSession.getScore() != null){ + sectionScore = sectionScore.add(itemSession.getScore()); + } + } + + return sectionScore; + } + private Long toDurationInMilliseconds(Long value) { if(value == null || value.longValue() == 0) return null; return value.longValue() / 1000l; } - private List<ItemInfos> getItemInfos() { - if(itemInfos == null) { - itemInfos = new ArrayList<>(); - List<AssessmentItemRef> itemRefs = resolvedAssessmentTest.getAssessmentItemRefs(); - for(AssessmentItemRef itemRef:itemRefs) { + private List<AbstractInfos> getItemInfos() { + if(elementInfos == null) { + numOfSections = 0; + elementInfos = new ArrayList<>(); + + AssessmentTest assessmentTest = resolvedAssessmentTest.getRootNodeLookup().extractAssumingSuccessful(); + for(TestPart part:assessmentTest.getTestParts()) { + for(AssessmentSection section:part.getAssessmentSections()) { + collectElementInfos(section); + } + } + } + return elementInfos; + } + + private void collectElementInfos(AssessmentSection section) { + numOfSections++; + SectionInfos sectionInfos = new SectionInfos(section); + elementInfos.add(sectionInfos); + + List<SectionPart> parts = section.getChildAbstractParts(); + for(SectionPart part:parts) { + if(part instanceof AssessmentItemRef) { + AssessmentItemRef itemRef = (AssessmentItemRef)part; ResolvedAssessmentItem resolvedItem = resolvedAssessmentTest.getResolvedAssessmentItem(itemRef); AssessmentItem item = resolvedItem.getRootNodeLookup().extractIfSuccessful(); if(item != null) { - itemInfos.add(new ItemInfos(itemRef, item, item.getItemBody().findInteractions())); + ItemInfos itemInfo = new ItemInfos(itemRef, item, item.getItemBody().findInteractions()); + elementInfos.add(itemInfo); + sectionInfos.getItemInfos().add(itemInfo); } + } else if(part instanceof AssessmentSection) { + collectElementInfos((AssessmentSection)part); } } - return itemInfos; } private static class SessionResponses { @@ -667,7 +742,28 @@ public class QTI21ArchiveFormat { } } - private static class ItemInfos { + private static class AbstractInfos { + // + } + + private static class SectionInfos extends AbstractInfos { + private final AssessmentSection section; + private final List<ItemInfos> itemInfos = new ArrayList<>(); + + public SectionInfos(AssessmentSection section) { + this.section = section; + } + + public AssessmentSection getAssessmentSection() { + return section; + } + + public List<ItemInfos> getItemInfos() { + return itemInfos; + } + } + + private static class ItemInfos extends AbstractInfos { private final AssessmentItemRef itemRef; private final AssessmentItem assessmentItem; diff --git a/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_de.properties index 04cddbbfcbf..037467d2bc4 100644 --- a/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_de.properties @@ -16,6 +16,7 @@ archive.table.header.node=Kurs archive.table.header.node.passed=Kursbaustein bestanden archive.table.header.node.points=Kursbaustein Punkte archive.table.header.points=$\:table.header.score +archive.table.header.section=Sektion "{0}" archive.table.header.test=Test assessment.comment.legend=Pers\u00F6nliche Notizen assessment.comment.legend.help=Sie k\u00F6nnen ein Kommentar hier schreiebn. Diese Notizen sind privat und werden nicht in einem Pr\u00FCfung ber\u00FCcksichtigt. diff --git a/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_en.properties index 5d833289106..53747311d37 100644 --- a/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_en.properties @@ -16,6 +16,7 @@ archive.table.header.node=Course archive.table.header.node.passed=Passed course element archive.table.header.node.points=Score course element archive.table.header.points=$\:table.header.score +archive.table.header.section=Section "{0}" archive.table.header.test=Test assessment.comment.legend=Personal notes assessment.comment.legend.help=Please use the following text box if you need to provide any additional information, comments or feedback during this test. This notice is private and will be taken in account for an exam. diff --git a/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_fr.properties b/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_fr.properties index 3ea7ffe9fd7..7168ff94852 100644 --- a/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_fr.properties +++ b/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_fr.properties @@ -16,6 +16,7 @@ archive.table.header.node=Cours archive.table.header.node.passed=Element de cours r\u00E9ussi archive.table.header.node.points=Points \u00E9l\u00E9ment de cours archive.table.header.points=$\:table.header.score +archive.table.header.section=Sektion "{0}" archive.table.header.test=Test assessment.comment.legend=Notes personelles assessment.comment.legend.help=Vous pouvez ajoutez un commentaire dans le champ de texte ci-dessous. Cette note est priv\u00E9e et il n'en sera pas tenu compte dans un test. -- GitLab