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