From 453e6b590795531bae54bf4f275802dba0d99106 Mon Sep 17 00:00:00 2001
From: srosse <none@none>
Date: Tue, 16 Feb 2016 20:07:54 +0100
Subject: [PATCH] OO-1593: can import a question with the CSV import

---
 .../model/xml/AssessmentItemBuilder.java      |   7 +
 .../model/xml/AssessmentItemFactory.java      |  21 +-
 .../ChoiceAssessmentItemBuilder.java          |   7 +
 .../EssayAssessmentItemBuilder.java           |  12 +-
 .../FIBAssessmentItemBuilder.java             | 101 ++++
 .../KPrimAssessmentItemBuilder.java           |   8 +-
 .../MultipleChoiceAssessmentItemBuilder.java  |  14 +-
 .../SingleChoiceAssessmentItemBuilder.java    |  10 +-
 .../AssessmentItemAndMetadata.java            | 185 +++++++
 .../AssessmentItemsPackage.java               |  41 ++
 .../CSVToQuestionConverter.java               | 450 ++++++++++++++++++
 .../qti21/questionimport/ImportOptions.java   |  43 ++
 .../OverviewQuestionController.java           | 150 ++++++
 .../questionimport/QImport_1_InputStep.java   |  59 +++
 .../QImport_2_OverviewStep.java               |  64 +++
 .../questionimport/TextInputController.java   | 123 +++++
 .../questionimport/_content/example.html      |   4 +
 .../_i18n/LocalStrings_de.properties          |   1 +
 .../_i18n/LocalStrings_en.properties          |  14 +
 .../AssessmentTestComposerController.java     | 114 ++++-
 .../SingleChoiceEditorController.java         |   2 +-
 .../model/xml/AssessmentItemPackageTest.java  |   2 +-
 22 files changed, 1395 insertions(+), 37 deletions(-)
 create mode 100644 src/main/java/org/olat/ims/qti21/model/xml/interactions/FIBAssessmentItemBuilder.java
 create mode 100644 src/main/java/org/olat/ims/qti21/questionimport/AssessmentItemAndMetadata.java
 create mode 100644 src/main/java/org/olat/ims/qti21/questionimport/AssessmentItemsPackage.java
 create mode 100644 src/main/java/org/olat/ims/qti21/questionimport/CSVToQuestionConverter.java
 create mode 100644 src/main/java/org/olat/ims/qti21/questionimport/ImportOptions.java
 create mode 100644 src/main/java/org/olat/ims/qti21/questionimport/OverviewQuestionController.java
 create mode 100644 src/main/java/org/olat/ims/qti21/questionimport/QImport_1_InputStep.java
 create mode 100644 src/main/java/org/olat/ims/qti21/questionimport/QImport_2_OverviewStep.java
 create mode 100644 src/main/java/org/olat/ims/qti21/questionimport/TextInputController.java
 create mode 100644 src/main/java/org/olat/ims/qti21/questionimport/_content/example.html
 create mode 100644 src/main/java/org/olat/ims/qti21/questionimport/_i18n/LocalStrings_de.properties
 create mode 100644 src/main/java/org/olat/ims/qti21/questionimport/_i18n/LocalStrings_en.properties

diff --git a/src/main/java/org/olat/ims/qti21/model/xml/AssessmentItemBuilder.java b/src/main/java/org/olat/ims/qti21/model/xml/AssessmentItemBuilder.java
index 55fe5c6eeec..04c928dd20c 100644
--- a/src/main/java/org/olat/ims/qti21/model/xml/AssessmentItemBuilder.java
+++ b/src/main/java/org/olat/ims/qti21/model/xml/AssessmentItemBuilder.java
@@ -26,6 +26,7 @@ import java.util.ArrayList;
 import java.util.List;
 
 import org.olat.ims.qti21.QTI21Constants;
+import org.olat.ims.qti21.model.QTI21QuestionType;
 
 import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem;
 import uk.ac.ed.ph.jqtiplus.node.item.ModalFeedback;
@@ -73,6 +74,8 @@ public abstract class AssessmentItemBuilder {
 		return assessmentItem;
 	}
 	
+	public abstract QTI21QuestionType getQuestionType();
+	
 	protected void extract() {
 		extractMinScore();
 		extractMaxScore();
@@ -131,6 +134,10 @@ public abstract class AssessmentItemBuilder {
 		assessmentItem.setTitle(title);
 	}
 	
+	public abstract String getQuestion();
+	
+	public abstract void setQuestion(String question);
+	
 	public ScoreBuilder getMinScoreBuilder() {
 		return minScoreBuilder;
 	}
diff --git a/src/main/java/org/olat/ims/qti21/model/xml/AssessmentItemFactory.java b/src/main/java/org/olat/ims/qti21/model/xml/AssessmentItemFactory.java
index c0f62ab97b4..528eddb68c6 100644
--- a/src/main/java/org/olat/ims/qti21/model/xml/AssessmentItemFactory.java
+++ b/src/main/java/org/olat/ims/qti21/model/xml/AssessmentItemFactory.java
@@ -31,6 +31,7 @@ import java.util.Map;
 import org.olat.core.helpers.Settings;
 import org.olat.ims.qti21.QTI21Constants;
 import org.olat.ims.qti21.model.IdentifierGenerator;
+import org.olat.ims.qti21.model.QTI21QuestionType;
 
 import uk.ac.ed.ph.jqtiplus.group.NodeGroupList;
 import uk.ac.ed.ph.jqtiplus.group.item.ItemBodyGroup;
@@ -92,7 +93,7 @@ import uk.ac.ed.ph.jqtiplus.value.IdentifierValue;
 public class AssessmentItemFactory {
 	
 	public static AssessmentItem createSingleChoice() {
-		AssessmentItem assessmentItem = createAssessmentItem("Single choice");
+		AssessmentItem assessmentItem = createAssessmentItem(QTI21QuestionType.sc, "Single choice");
 
 		//define correct answer
 		Identifier responseDeclarationId = Identifier.assumedLegal("RESPONSE_1");
@@ -106,7 +107,7 @@ public class AssessmentItemFactory {
 		//the single choice interaction
 		ItemBody itemBody = appendDefaultItemBody(assessmentItem);
 		ChoiceInteraction choiceInteraction = appendChoiceInteraction(itemBody, responseDeclarationId, 1, true);
-		appendSimpleChoice(choiceInteraction, "sc");
+		appendSimpleChoice(choiceInteraction, "New answer", "sc");
 
 		//response processing
 		ResponseProcessing responseProcessing = createResponseProcessing(assessmentItem, responseDeclarationId);
@@ -114,9 +115,13 @@ public class AssessmentItemFactory {
 		return assessmentItem;
 	}
 	
-	public static AssessmentItem createAssessmentItem(String defaultTitle) {
+	public static AssessmentItem createAssessmentItem(QTI21QuestionType type, String defaultTitle) {
 		AssessmentItem assessmentItem = new AssessmentItem();
-		assessmentItem.setIdentifier(IdentifierGenerator.newAsString("item"));
+		if(type != null) {
+			assessmentItem.setIdentifier(IdentifierGenerator.newAsString(type.getPrefix()));
+		} else {
+			assessmentItem.setIdentifier(IdentifierGenerator.newAsString("item"));
+		}
 		assessmentItem.setTitle(defaultTitle);
 		assessmentItem.setToolName(QTI21Constants.TOOLNAME);
 		assessmentItem.setToolVersion(Settings.getVersion());
@@ -309,16 +314,16 @@ public class AssessmentItemFactory {
 		return responseDeclaration;
 	}
 	
-	public static SimpleChoice createSimpleChoice(ChoiceInteraction choiceInteraction, String prefix) {
+	public static SimpleChoice createSimpleChoice(ChoiceInteraction choiceInteraction, String text, String prefix) {
 		SimpleChoice newChoice = new SimpleChoice(choiceInteraction);
 		newChoice.setIdentifier(IdentifierGenerator.newAsIdentifier(prefix));
-		P firstChoiceText = AssessmentItemFactory.getParagraph(newChoice, "New answer");
+		P firstChoiceText = AssessmentItemFactory.getParagraph(newChoice, text);
 		newChoice.getFlowStatics().add(firstChoiceText);
 		return newChoice;
 	}
 	
-	public static SimpleChoice appendSimpleChoice(ChoiceInteraction choiceInteraction, String prefix) {
-		SimpleChoice newChoice = createSimpleChoice(choiceInteraction, prefix);
+	public static SimpleChoice appendSimpleChoice(ChoiceInteraction choiceInteraction, String text, String prefix) {
+		SimpleChoice newChoice = createSimpleChoice(choiceInteraction, text, prefix);
 		choiceInteraction.getNodeGroups().getSimpleChoiceGroup().getSimpleChoices().add(newChoice);
 		return newChoice;
 	}
diff --git a/src/main/java/org/olat/ims/qti21/model/xml/interactions/ChoiceAssessmentItemBuilder.java b/src/main/java/org/olat/ims/qti21/model/xml/interactions/ChoiceAssessmentItemBuilder.java
index 0623f2d9fe1..7f8ed39286d 100644
--- a/src/main/java/org/olat/ims/qti21/model/xml/interactions/ChoiceAssessmentItemBuilder.java
+++ b/src/main/java/org/olat/ims/qti21/model/xml/interactions/ChoiceAssessmentItemBuilder.java
@@ -187,6 +187,13 @@ public abstract class ChoiceAssessmentItemBuilder extends AssessmentItemBuilder
 		return choices;
 	}
 	
+	public void addSimpleChoice(SimpleChoice choice) {
+		if(choices == null) {
+			choices = new ArrayList<>();
+		}
+		choices.add(choice);
+	}
+	
 	public void setSimpleChoices(List<SimpleChoice> choices) {
 		this.choices = new ArrayList<>(choices);
 	}
diff --git a/src/main/java/org/olat/ims/qti21/model/xml/interactions/EssayAssessmentItemBuilder.java b/src/main/java/org/olat/ims/qti21/model/xml/interactions/EssayAssessmentItemBuilder.java
index c39d6b159e6..f6038794969 100644
--- a/src/main/java/org/olat/ims/qti21/model/xml/interactions/EssayAssessmentItemBuilder.java
+++ b/src/main/java/org/olat/ims/qti21/model/xml/interactions/EssayAssessmentItemBuilder.java
@@ -31,6 +31,7 @@ import javax.xml.transform.stream.StreamResult;
 
 import org.olat.core.gui.render.StringOutput;
 import org.olat.ims.qti21.QTI21Constants;
+import org.olat.ims.qti21.model.QTI21QuestionType;
 import org.olat.ims.qti21.model.xml.AssessmentItemBuilder;
 import org.olat.ims.qti21.model.xml.AssessmentItemFactory;
 
@@ -73,7 +74,7 @@ public class EssayAssessmentItemBuilder extends AssessmentItemBuilder {
 	}
 	
 	private static AssessmentItem createAssessmentItem() {
-		AssessmentItem assessmentItem = AssessmentItemFactory.createAssessmentItem("Essay");
+		AssessmentItem assessmentItem = AssessmentItemFactory.createAssessmentItem(QTI21QuestionType.essay, "Essay");
 		
 		//define the response
 		Identifier responseDeclarationId = Identifier.assumedLegal("RESPONSE_1");
@@ -92,6 +93,11 @@ public class EssayAssessmentItemBuilder extends AssessmentItemBuilder {
 		return assessmentItem;
 	}
 	
+	@Override
+	public QTI21QuestionType getQuestionType() {
+		return QTI21QuestionType.essay;
+	}
+	
 	@Override
 	public void extract() {
 		super.extract();
@@ -112,11 +118,13 @@ public class EssayAssessmentItemBuilder extends AssessmentItemBuilder {
 		}
 		question = sb.toString();
 	}
-	
+
+	@Override
 	public String getQuestion() {
 		return question;
 	}
 	
+	@Override
 	public void setQuestion(String html) {
 		this.question = html;
 	}
diff --git a/src/main/java/org/olat/ims/qti21/model/xml/interactions/FIBAssessmentItemBuilder.java b/src/main/java/org/olat/ims/qti21/model/xml/interactions/FIBAssessmentItemBuilder.java
new file mode 100644
index 00000000000..9b7be7afd3c
--- /dev/null
+++ b/src/main/java/org/olat/ims/qti21/model/xml/interactions/FIBAssessmentItemBuilder.java
@@ -0,0 +1,101 @@
+/**
+ * <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.model.xml.interactions;
+
+import static org.olat.ims.qti21.model.xml.AssessmentItemFactory.appendDefaultItemBody;
+import static org.olat.ims.qti21.model.xml.AssessmentItemFactory.appendDefaultOutcomeDeclarations;
+import static org.olat.ims.qti21.model.xml.AssessmentItemFactory.appendExtendedTextInteraction;
+import static org.olat.ims.qti21.model.xml.AssessmentItemFactory.createExtendedTextResponseDeclaration;
+import static org.olat.ims.qti21.model.xml.AssessmentItemFactory.createResponseProcessing;
+
+import java.util.List;
+
+import org.olat.ims.qti21.model.QTI21QuestionType;
+import org.olat.ims.qti21.model.xml.AssessmentItemBuilder;
+import org.olat.ims.qti21.model.xml.AssessmentItemFactory;
+
+import uk.ac.ed.ph.jqtiplus.node.content.ItemBody;
+import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem;
+import uk.ac.ed.ph.jqtiplus.node.item.response.declaration.ResponseDeclaration;
+import uk.ac.ed.ph.jqtiplus.node.item.response.processing.ResponseProcessing;
+import uk.ac.ed.ph.jqtiplus.node.item.response.processing.ResponseRule;
+import uk.ac.ed.ph.jqtiplus.serialization.QtiSerializer;
+import uk.ac.ed.ph.jqtiplus.types.Identifier;
+
+/**
+ * 
+ * Initial date: 16.02.2016<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class FIBAssessmentItemBuilder extends AssessmentItemBuilder {
+
+	private String question;
+	
+	public FIBAssessmentItemBuilder(QtiSerializer qtiSerializer) {
+		super(createAssessmentItem(), qtiSerializer);
+	}
+	
+	public FIBAssessmentItemBuilder(AssessmentItem assessmentItem, QtiSerializer qtiSerializer) {
+		super(assessmentItem, qtiSerializer);
+	}
+	
+	private static AssessmentItem createAssessmentItem() {
+		AssessmentItem assessmentItem = AssessmentItemFactory.createAssessmentItem(QTI21QuestionType.fib, "FIB");
+		
+		//define the response
+		Identifier responseDeclarationId = Identifier.assumedLegal("RESPONSE_1");
+		ResponseDeclaration responseDeclaration = createExtendedTextResponseDeclaration(assessmentItem, responseDeclarationId);
+		assessmentItem.getNodeGroups().getResponseDeclarationGroup().getResponseDeclarations().add(responseDeclaration);
+	
+		//outcomes
+		appendDefaultOutcomeDeclarations(assessmentItem, 1.0d);
+		
+		ItemBody itemBody = appendDefaultItemBody(assessmentItem);
+		appendExtendedTextInteraction(itemBody, responseDeclarationId);
+		
+		//response processing
+		ResponseProcessing responseProcessing = createResponseProcessing(assessmentItem, responseDeclarationId);
+		assessmentItem.getNodeGroups().getResponseProcessingGroup().setResponseProcessing(responseProcessing);
+		return assessmentItem;
+	}
+	
+	@Override
+	public QTI21QuestionType getQuestionType() {
+		return QTI21QuestionType.fib;
+	}
+	
+	@Override
+	public String getQuestion() {
+		return question;
+	}
+	
+	@Override
+	public void setQuestion(String html) {
+		this.question = html;
+	}
+
+	@Override
+	protected void buildMainScoreRule(List<ResponseRule> responseRules) {
+		//
+	}
+	
+	
+}
diff --git a/src/main/java/org/olat/ims/qti21/model/xml/interactions/KPrimAssessmentItemBuilder.java b/src/main/java/org/olat/ims/qti21/model/xml/interactions/KPrimAssessmentItemBuilder.java
index 735f5b4bc98..078e8a6a5ed 100644
--- a/src/main/java/org/olat/ims/qti21/model/xml/interactions/KPrimAssessmentItemBuilder.java
+++ b/src/main/java/org/olat/ims/qti21/model/xml/interactions/KPrimAssessmentItemBuilder.java
@@ -35,6 +35,7 @@ import javax.xml.transform.stream.StreamResult;
 
 import org.olat.core.gui.render.StringOutput;
 import org.olat.ims.qti21.QTI21Constants;
+import org.olat.ims.qti21.model.QTI21QuestionType;
 import org.olat.ims.qti21.model.xml.AssessmentItemBuilder;
 import org.olat.ims.qti21.model.xml.AssessmentItemFactory;
 
@@ -91,7 +92,7 @@ public class KPrimAssessmentItemBuilder extends AssessmentItemBuilder {
 	}
 	
 	private static AssessmentItem createAssessmentItem() {
-		AssessmentItem assessmentItem = AssessmentItemFactory.createAssessmentItem("KPrim");
+		AssessmentItem assessmentItem = AssessmentItemFactory.createAssessmentItem(QTI21QuestionType.kprim, "KPrim");
 		
 		NodeGroupList nodeGroups = assessmentItem.getNodeGroups();
 
@@ -165,6 +166,11 @@ public class KPrimAssessmentItemBuilder extends AssessmentItemBuilder {
 		question = sb.toString();
 	}
 	
+	@Override
+	public QTI21QuestionType getQuestionType() {
+		return QTI21QuestionType.kprim;
+	}
+	
 	public boolean isShuffle() {
 		return shuffle;
 	}
diff --git a/src/main/java/org/olat/ims/qti21/model/xml/interactions/MultipleChoiceAssessmentItemBuilder.java b/src/main/java/org/olat/ims/qti21/model/xml/interactions/MultipleChoiceAssessmentItemBuilder.java
index 17bc09ecf23..0b55b9d192d 100644
--- a/src/main/java/org/olat/ims/qti21/model/xml/interactions/MultipleChoiceAssessmentItemBuilder.java
+++ b/src/main/java/org/olat/ims/qti21/model/xml/interactions/MultipleChoiceAssessmentItemBuilder.java
@@ -28,6 +28,7 @@ import java.util.List;
 
 import org.olat.ims.qti21.QTI21Constants;
 import org.olat.ims.qti21.model.IdentifierGenerator;
+import org.olat.ims.qti21.model.QTI21QuestionType;
 import org.olat.ims.qti21.model.xml.AssessmentItemFactory;
 
 import uk.ac.ed.ph.jqtiplus.group.NodeGroupList;
@@ -83,7 +84,7 @@ public class MultipleChoiceAssessmentItemBuilder extends ChoiceAssessmentItemBui
 	}
 	
 	private static AssessmentItem createAssessmentItem() {
-		AssessmentItem assessmentItem = AssessmentItemFactory.createAssessmentItem("Multiple choice");
+		AssessmentItem assessmentItem = AssessmentItemFactory.createAssessmentItem(QTI21QuestionType.mc, "Multiple choice");
 		
 		NodeGroupList nodeGroups = assessmentItem.getNodeGroups();
 
@@ -101,7 +102,7 @@ public class MultipleChoiceAssessmentItemBuilder extends ChoiceAssessmentItemBui
 		ItemBody itemBody = appendDefaultItemBody(assessmentItem);
 		ChoiceInteraction choiceInteraction = appendChoiceInteraction(itemBody, responseDeclarationId, 1, true);
 		
-		appendSimpleChoice(choiceInteraction, "mc");
+		appendSimpleChoice(choiceInteraction, "New answer", "mc");
 
 		//response processing
 		ResponseProcessing responseProcessing = createResponseProcessing(assessmentItem, responseDeclarationId);
@@ -134,6 +135,11 @@ public class MultipleChoiceAssessmentItemBuilder extends ChoiceAssessmentItemBui
 			}
 		}
 	}
+	
+	@Override
+	public QTI21QuestionType getQuestionType() {
+		return QTI21QuestionType.mc;
+	}
 
 	@Override
 	public boolean isCorrect(SimpleChoice choice) {
@@ -144,6 +150,10 @@ public class MultipleChoiceAssessmentItemBuilder extends ChoiceAssessmentItemBui
 		correctAnswers.clear();
 		correctAnswers.addAll(identifiers);
 	}
+	
+	public void addCorrectAnswer(Identifier identifier) {
+		correctAnswers.add(identifier);
+	}
 
 	@Override
 	protected void buildResponseDeclaration() {
diff --git a/src/main/java/org/olat/ims/qti21/model/xml/interactions/SingleChoiceAssessmentItemBuilder.java b/src/main/java/org/olat/ims/qti21/model/xml/interactions/SingleChoiceAssessmentItemBuilder.java
index de87d1322a0..fd5cd549957 100644
--- a/src/main/java/org/olat/ims/qti21/model/xml/interactions/SingleChoiceAssessmentItemBuilder.java
+++ b/src/main/java/org/olat/ims/qti21/model/xml/interactions/SingleChoiceAssessmentItemBuilder.java
@@ -30,6 +30,7 @@ import java.util.List;
 
 import org.olat.ims.qti21.QTI21Constants;
 import org.olat.ims.qti21.model.IdentifierGenerator;
+import org.olat.ims.qti21.model.QTI21QuestionType;
 import org.olat.ims.qti21.model.xml.AssessmentItemFactory;
 
 import uk.ac.ed.ph.jqtiplus.node.content.ItemBody;
@@ -82,7 +83,7 @@ public class SingleChoiceAssessmentItemBuilder extends ChoiceAssessmentItemBuild
 	}
 	
 	private static AssessmentItem createAssessmentItem() {
-		AssessmentItem assessmentItem = AssessmentItemFactory.createAssessmentItem("Single choice");
+		AssessmentItem assessmentItem = AssessmentItemFactory.createAssessmentItem(QTI21QuestionType.sc, "Single choice");
 
 		//define correct answer
 		Identifier responseDeclarationId = Identifier.assumedLegal("RESPONSE_1");
@@ -97,7 +98,7 @@ public class SingleChoiceAssessmentItemBuilder extends ChoiceAssessmentItemBuild
 		ItemBody itemBody = appendDefaultItemBody(assessmentItem);
 		ChoiceInteraction choiceInteraction = appendChoiceInteraction(itemBody, responseDeclarationId, 1, true);
 		
-		appendSimpleChoice(choiceInteraction, "sc");
+		appendSimpleChoice(choiceInteraction, "New answer", "sc");
 
 		//response processing
 		ResponseProcessing responseProcessing = createResponseProcessing(assessmentItem, responseDeclarationId);
@@ -122,6 +123,11 @@ public class SingleChoiceAssessmentItemBuilder extends ChoiceAssessmentItemBuild
 			}
 		}
 	}
+	
+	@Override
+	public QTI21QuestionType getQuestionType() {
+		return QTI21QuestionType.sc;
+	}
 
 	@Override
 	public boolean isCorrect(SimpleChoice choice) {
diff --git a/src/main/java/org/olat/ims/qti21/questionimport/AssessmentItemAndMetadata.java b/src/main/java/org/olat/ims/qti21/questionimport/AssessmentItemAndMetadata.java
new file mode 100644
index 00000000000..98b53ddf53d
--- /dev/null
+++ b/src/main/java/org/olat/ims/qti21/questionimport/AssessmentItemAndMetadata.java
@@ -0,0 +1,185 @@
+/**
+ * <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.questionimport;
+
+import java.math.BigDecimal;
+
+import org.olat.ims.qti21.model.QTI21QuestionType;
+import org.olat.ims.qti21.model.xml.AssessmentItemBuilder;
+
+/**
+ * 
+ * Initial date: 16.02.2016<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class AssessmentItemAndMetadata {
+	
+	private final AssessmentItemBuilder item;
+	
+	private QTI21QuestionType questionType;
+	private String language;
+	private String taxonomyPath;
+	private String keywords;
+	private String coverage;
+	private String level;
+	private String typicalLearningTime;
+	private String license;
+	private String editor;
+	private String editorVersion;
+	private int numOfAnswerAlternatives;
+	private BigDecimal difficulty;
+	private BigDecimal differentiation;
+	private BigDecimal stdevDifficulty;
+
+	private boolean hasError;
+	
+	public AssessmentItemAndMetadata(AssessmentItemBuilder item) {
+		this.item = item;
+	}
+
+	public AssessmentItemBuilder getItemBuilder() {
+		return item;
+	}
+	
+	public BigDecimal getDifficulty() {
+		return difficulty;
+	}
+
+	public void setDifficulty(BigDecimal difficulty) {
+		this.difficulty = difficulty;
+	}
+
+	public BigDecimal getStdevDifficulty() {
+		return stdevDifficulty;
+	}
+
+	public void setStdevDifficulty(BigDecimal stdevDifficulty) {
+		this.stdevDifficulty = stdevDifficulty;
+	}
+
+	public BigDecimal getDifferentiation() {
+		return differentiation;
+	}
+
+	public void setDifferentiation(BigDecimal differentiation) {
+		this.differentiation = differentiation;
+	}
+
+	public int getNumOfAnswerAlternatives() {
+		return numOfAnswerAlternatives;
+	}
+
+	public void setNumOfAnswerAlternatives(int numOfAnswerAlternatives) {
+		this.numOfAnswerAlternatives = numOfAnswerAlternatives;
+	}
+
+	public String getEditor() {
+		return editor;
+	}
+
+	public void setEditor(String editor) {
+		this.editor = editor;
+	}
+
+	public String getEditorVersion() {
+		return editorVersion;
+	}
+
+	public void setEditorVersion(String editorVersion) {
+		this.editorVersion = editorVersion;
+	}
+
+	public String getLicense() {
+		return license;
+	}
+
+	public void setLicense(String license) {
+		this.license = license;
+	}
+
+	public String getLevel() {
+		return level;
+	}
+
+	public void setLevel(String level) {
+		this.level = level;
+	}
+
+	public String getTypicalLearningTime() {
+		return typicalLearningTime;
+	}
+
+	public void setTypicalLearningTime(String typicalLearningTime) {
+		this.typicalLearningTime = typicalLearningTime;
+	}
+
+	public String getCoverage() {
+		return coverage;
+	}
+
+	public void setCoverage(String coverage) {
+		this.coverage = coverage;
+	}
+
+	public String getLanguage() {
+		return language;
+	}
+
+	public void setLanguage(String language) {
+		this.language = language;
+	}
+
+	public String getKeywords() {
+		return keywords;
+	}
+
+	public void setKeywords(String keywords) {
+		this.keywords = keywords;
+	}
+
+	public String getTaxonomyPath() {
+		return taxonomyPath;
+	}
+
+	public void setTaxonomyPath(String taxonomyPath) {
+		this.taxonomyPath = taxonomyPath;
+	}
+
+	/**
+	 * 
+	 * @return The type defined in org.olat.ims.qti.editor.beecom.objects.Question.TYPE_*
+	 */
+	public QTI21QuestionType getQuestionType() {
+		return questionType;
+	}
+
+	public void setTitle(String title) {
+		item.setTitle(title);
+	}
+
+	public boolean isHasError() {
+		return hasError;
+	}
+
+	public void setHasError(boolean hasError) {
+		this.hasError = hasError;
+	}
+}
diff --git a/src/main/java/org/olat/ims/qti21/questionimport/AssessmentItemsPackage.java b/src/main/java/org/olat/ims/qti21/questionimport/AssessmentItemsPackage.java
new file mode 100644
index 00000000000..826ee41a45a
--- /dev/null
+++ b/src/main/java/org/olat/ims/qti21/questionimport/AssessmentItemsPackage.java
@@ -0,0 +1,41 @@
+/**
+ * <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.questionimport;
+
+import java.util.List;
+
+/**
+ * 
+ * Initial date: 24.09.2014<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class AssessmentItemsPackage {
+	
+	private List<AssessmentItemAndMetadata> items;
+
+	public List<AssessmentItemAndMetadata> getItems() {
+		return items;
+	}
+
+	public void setItems(List<AssessmentItemAndMetadata> items) {
+		this.items = items;
+	}
+}
diff --git a/src/main/java/org/olat/ims/qti21/questionimport/CSVToQuestionConverter.java b/src/main/java/org/olat/ims/qti21/questionimport/CSVToQuestionConverter.java
new file mode 100644
index 00000000000..a2a2dd223cc
--- /dev/null
+++ b/src/main/java/org/olat/ims/qti21/questionimport/CSVToQuestionConverter.java
@@ -0,0 +1,450 @@
+/**
+ * <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.questionimport;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
+import org.olat.core.util.StringHelper;
+import org.olat.ims.qti21.model.xml.AssessmentItemBuilder;
+import org.olat.ims.qti21.model.xml.AssessmentItemFactory;
+import org.olat.ims.qti21.model.xml.interactions.ChoiceAssessmentItemBuilder;
+import org.olat.ims.qti21.model.xml.interactions.FIBAssessmentItemBuilder;
+import org.olat.ims.qti21.model.xml.interactions.MultipleChoiceAssessmentItemBuilder;
+import org.olat.ims.qti21.model.xml.interactions.SingleChoiceAssessmentItemBuilder;
+import org.olat.ims.qti21.model.xml.interactions.ChoiceAssessmentItemBuilder.ScoreEvaluation;
+
+import uk.ac.ed.ph.jqtiplus.node.item.interaction.ChoiceInteraction;
+import uk.ac.ed.ph.jqtiplus.node.item.interaction.choice.SimpleChoice;
+import uk.ac.ed.ph.jqtiplus.serialization.QtiSerializer;
+
+/**
+ * 
+ * Initial date: 24.09.2014<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class CSVToQuestionConverter {
+	
+	private static final OLog log = Tracing.createLoggerFor(CSVToQuestionConverter.class);
+
+	private ImportOptions options;
+	private final QtiSerializer qtiSerializer;
+	private AssessmentItemAndMetadata currentItem;
+	private final List<AssessmentItemAndMetadata> items = new ArrayList<>();
+	
+	public CSVToQuestionConverter(ImportOptions options, QtiSerializer qtiSerializer) {
+		this.options = options;
+		this.qtiSerializer = qtiSerializer;
+	}
+	
+	public List<AssessmentItemAndMetadata> getItems() {
+		return items;
+	}
+	
+	public void parse(String input) {
+		String[] lines = input.split("\r?\n");
+		
+		for (int i = 0; i<lines.length; i++) {
+			String line = lines[i];
+			if (line.equals("")) {
+				continue;
+			}
+		
+			String delimiter = "\t";
+			// use comma as fallback delimiter, e.g. for better testing
+			if (line.indexOf(delimiter) == -1) {
+				delimiter = ",";
+			}
+			String[] parts = line.split(delimiter);
+			if(parts.length > 1) {
+				processLine(parts);
+			}	
+		}
+		
+		if(currentItem != null) {
+			currentItem.getItemBuilder().build();
+			items.add(currentItem);
+			currentItem = null;
+		}
+	}
+	
+	private void processLine(String[] parts) {
+		String marker = parts[0].toLowerCase();
+		switch(marker) {
+			case "typ":
+			case "type": processType(parts); break;
+			case "titel":
+			case "title": processTitle(parts); break;
+			case "beschreibung":
+			case "description": break;
+			case "frage":
+			case "question": processQuestion(parts); break;
+			case "punkte":
+			case "points": processPoints(parts); break;
+			case "fachbereich":
+			case "subject": processTaxonomyPath(parts); break;
+			case "feedback correct answer": processFeedbackCorrectAnswer(parts); break;
+			case "feedback wrong answer": processFeedbackWrongAnswer(parts); break;
+			case "schlagworte":
+			case "keywords": processKeywords(parts); break;
+			case "abdeckung":
+			case "coverage": processCoverage(parts); break;
+			case "level": processLevel(parts); break;
+			case "sprache":
+			case "language": processLanguage(parts); break;
+			case "durchschnittliche bearbeitungszeit":
+			case "typical learning time": processTypicalLearningTime(parts); break;
+			case "itemschwierigkeit":
+			case "difficulty index": processDifficultyIndex(parts); break;
+			case "standardabweichung itemschwierigkeit":
+			case "standard deviation": processStandardDeviation(parts); break;
+			case "trennsch\u00E4rfe":
+			case "discrimination index": processDiscriminationIndex(parts); break;
+			case "anzahl distraktoren":
+			case "distractors": processDistractors(parts); break;
+			case "editor": processEditor(parts); break;
+			case "editor version": processEditorVersion(parts); break;
+			case "lizenz":
+			case "license": processLicense(parts); break;
+			default: processChoice(parts);
+		}
+	}
+
+	private void processLevel(String[] parts) {
+		if(currentItem == null || parts.length < 2) return;
+		
+		String level = parts[1];
+		if(StringHelper.containsNonWhitespace(level)) {
+			currentItem.setLevel(level.trim());
+		}
+	}
+	
+	private void processTypicalLearningTime(String[] parts) {
+		if(currentItem == null || parts.length < 2) return;
+		
+		String time = parts[1];
+		if(StringHelper.containsNonWhitespace(time)) {
+			currentItem.setTypicalLearningTime(time.trim());
+		}
+	}
+	
+	private void processLicense(String[] parts) {
+		if(currentItem == null || parts.length < 2) return;
+		
+		String license = parts[1];
+		if(StringHelper.containsNonWhitespace(license)) {
+			currentItem.setLicense(license.trim());
+		}
+	}
+	
+	private void processEditor(String[] parts) {
+		if(currentItem == null || parts.length < 2) return;
+		
+		String editor = parts[1];
+		if(StringHelper.containsNonWhitespace(editor)) {
+			currentItem.setEditor(editor.trim());
+		}
+	}
+	
+	private void processEditorVersion(String[] parts) {
+		if(currentItem == null || parts.length < 2) return;
+		
+		String editorVersion = parts[1];
+		if(StringHelper.containsNonWhitespace(editorVersion)) {
+			currentItem.setEditorVersion(editorVersion.trim());
+		}
+	}
+	
+	private void processFeedbackCorrectAnswer(String[] parts) {
+		if(currentItem == null || parts.length < 2) return;
+		
+		String feedback = parts[1];
+		if(StringHelper.containsNonWhitespace(feedback)) {
+			AssessmentItemBuilder itemBuilder = currentItem.getItemBuilder();
+			itemBuilder.createCorrectFeedback().setText(feedback);
+		}
+	}
+	
+	private void processFeedbackWrongAnswer(String[] parts) {
+		if(currentItem == null || parts.length < 2) return;
+		
+		String feedback = parts[1];
+		if(StringHelper.containsNonWhitespace(feedback)) {
+			AssessmentItemBuilder itemBuilder = currentItem.getItemBuilder();
+			itemBuilder.createIncorrectFeedback().setText(feedback);
+		}
+	}
+	
+	private void processDistractors(String[] parts) {
+		if(currentItem == null || parts.length < 2) return;
+		
+		String distractors = parts[1];
+		if(StringHelper.containsNonWhitespace(distractors)) {
+			try {
+				currentItem.setNumOfAnswerAlternatives(Integer.parseInt(distractors.trim()));
+			} catch (NumberFormatException e) {
+				log.warn("", e);
+			}
+		}
+	}
+	
+	private void processDiscriminationIndex(String[] parts) {
+		if(currentItem == null || parts.length < 2) return;
+		
+		String discriminationIndex = parts[1];
+		if(StringHelper.containsNonWhitespace(discriminationIndex)) {
+			try {
+				currentItem.setDifferentiation(new BigDecimal(discriminationIndex.trim()));
+			} catch (Exception e) {
+				log.warn("", e);
+			}
+		}
+	}
+	
+	private void processDifficultyIndex(String[] parts) {
+		if(currentItem == null || parts.length < 2) return;
+		
+		String difficulty = parts[1];
+		if(StringHelper.containsNonWhitespace(difficulty)) {
+			try {
+				BigDecimal dif = new BigDecimal(difficulty.trim());
+				if(dif.doubleValue() >= 0.0d && dif.doubleValue() <= 1.0d) {
+					currentItem.setDifficulty(dif);
+				} else {
+					currentItem.setHasError(true);
+				}
+			} catch (Exception e) {
+				log.warn("", e);
+			}
+		}
+	}
+	
+	private void processStandardDeviation(String[] parts) {
+		if(currentItem == null || parts.length < 2) return;
+		
+		String stddev = parts[1];
+		if(StringHelper.containsNonWhitespace(stddev)) {
+			try {
+				BigDecimal dev = new BigDecimal(stddev.trim());
+				if(dev.doubleValue() >= 0.0d && dev.doubleValue() <= 1.0d) {
+					currentItem.setStdevDifficulty(dev);
+				} else {
+					currentItem.setHasError(true);
+				}
+			} catch (Exception e) {
+				log.warn("", e);
+			}
+		}
+	}
+	
+	private void processType(String[] parts) {
+		if(currentItem != null) {
+			items.add(currentItem);
+		}
+		
+		if(parts.length > 1) {
+			String type = parts[1].toLowerCase();
+			AssessmentItemBuilder itemBuilder;
+			switch(type) {
+				case "fib": {
+					FIBAssessmentItemBuilder fibItemBuilder = new FIBAssessmentItemBuilder(qtiSerializer);
+					itemBuilder = fibItemBuilder;
+					break;
+				}
+				case "mc": {
+					MultipleChoiceAssessmentItemBuilder mcItemBuilder = new MultipleChoiceAssessmentItemBuilder(qtiSerializer);
+					mcItemBuilder.setSimpleChoices(Collections.emptyList());
+					mcItemBuilder.clearMapping();
+					mcItemBuilder.setShuffle(options.isShuffle());
+					mcItemBuilder.setScoreEvaluationMode(ScoreEvaluation.perAnswer);
+					itemBuilder = mcItemBuilder;
+					break;
+				}
+				case "sc": {
+					SingleChoiceAssessmentItemBuilder scItemBuilder = new SingleChoiceAssessmentItemBuilder(qtiSerializer);
+					scItemBuilder.setSimpleChoices(Collections.emptyList());
+					scItemBuilder.clearMapping();
+					scItemBuilder.setShuffle(options.isShuffle());
+					scItemBuilder.setScoreEvaluationMode(ScoreEvaluation.perAnswer);
+					itemBuilder = scItemBuilder;
+					break;
+				}
+				default: {
+					itemBuilder = null;
+				}
+			}
+			
+			if(itemBuilder != null) {
+				currentItem = new AssessmentItemAndMetadata(itemBuilder);
+			} else {
+				log.warn("Question type not supported: " + type);
+				currentItem = null;
+			}
+		}
+	}
+	
+	private void processCoverage(String[] parts) {
+		if(currentItem == null || parts.length < 2) return;
+		
+		String coverage = parts[1];
+		if(StringHelper.containsNonWhitespace(coverage)) {
+			currentItem.setCoverage(coverage);
+		}
+	}
+	
+	private void processKeywords(String[] parts) {
+		if(currentItem == null || parts.length < 2) return;
+		
+		String keywords = parts[1];
+		if(StringHelper.containsNonWhitespace(keywords)) {
+			currentItem.setKeywords(keywords);
+		}
+	}
+	
+	private void processTaxonomyPath(String[] parts) {
+		if(currentItem == null || parts.length < 2) return;
+		
+		String taxonomyPath = parts[1];
+		if(StringHelper.containsNonWhitespace(taxonomyPath)) {
+			currentItem.setTaxonomyPath(taxonomyPath);
+		}
+	}
+	
+	private void processLanguage(String[] parts) {
+		if(currentItem == null || parts.length < 2) return;
+		
+		String language = parts[1];
+		if(StringHelper.containsNonWhitespace(language)) {
+			currentItem.setLanguage(language);
+		}
+	}
+	
+	private void processTitle(String[] parts) {
+		if(currentItem == null || parts.length < 2) return;
+		
+		String title = parts[1];
+		if(StringHelper.containsNonWhitespace(title)) {
+			currentItem.setTitle(title);
+		}
+	}
+	
+	private void processQuestion(String[] parts) {
+		if(currentItem == null) return;
+		
+		String content = parts[1];
+		if(StringHelper.containsNonWhitespace(content)) {
+			AssessmentItemBuilder itemBuilder = currentItem.getItemBuilder();
+			itemBuilder.setQuestion("<p>" + content + "</p>");
+		}
+	}
+	
+	private void processPoints(String[] parts) {
+		if(currentItem == null) return;
+		
+		double points = parseFloat(parts[1], 1.0f);
+		AssessmentItemBuilder itemBuilder = currentItem.getItemBuilder();
+		if (itemBuilder instanceof ChoiceAssessmentItemBuilder) {
+			itemBuilder.setMinScore(0.0d);
+			itemBuilder.setMaxScore(points);
+		} else if(itemBuilder instanceof FIBAssessmentItemBuilder) {
+			itemBuilder.setMinScore(0.0d);
+			itemBuilder.setMaxScore(points);
+		}
+	}
+	
+	private void processChoice(String[] parts) {
+		if(currentItem == null || parts.length < 2) {
+			return;
+		}
+		
+		try {
+			AssessmentItemBuilder itemBuilder = currentItem.getItemBuilder();
+			if (itemBuilder instanceof ChoiceAssessmentItemBuilder) {
+				double point = parseFloat(parts[0], 1.0f);
+				String content = parts[1];
+
+				ChoiceAssessmentItemBuilder choiceBuilder = (ChoiceAssessmentItemBuilder)itemBuilder;
+				ChoiceInteraction interaction = choiceBuilder.getChoiceInteraction();
+				SimpleChoice newChoice = AssessmentItemFactory
+						.createSimpleChoice(interaction, content, choiceBuilder.getQuestionType().getPrefix());
+				choiceBuilder.addSimpleChoice(newChoice);
+				choiceBuilder.setMapping(newChoice.getIdentifier(), point);
+
+				if(point > 0.0) {
+					if (itemBuilder instanceof MultipleChoiceAssessmentItemBuilder) {
+						((MultipleChoiceAssessmentItemBuilder)itemBuilder).addCorrectAnswer(newChoice.getIdentifier());
+					} else if (itemBuilder instanceof SingleChoiceAssessmentItemBuilder) {
+						((SingleChoiceAssessmentItemBuilder)itemBuilder).setCorrectAnswer(newChoice.getIdentifier());
+					}
+				}
+			} else if(itemBuilder instanceof FIBAssessmentItemBuilder) {
+				String firstPart = parts[0].toLowerCase();
+				FIBAssessmentItemBuilder fibBuilder = (FIBAssessmentItemBuilder)itemBuilder;
+				if("text".equals(firstPart) || "texte".equals(firstPart)) {
+					String text = parts[1];
+					
+					//fibBuilder.getResponses().add(response);
+				} else {
+					float point = parseFloat(parts[0], 1.0f);
+					String correctBlank = parts[1];
+					/*
+					FIBResponse response = new FIBResponse();
+					response.setType(FIBResponse.TYPE_BLANK);
+					response.setCorrectBlank(correctBlank);
+					response.setPoints(point);
+					
+					if(parts.length > 2) {
+						String sizes = parts[2];
+						String[] sizeArr = sizes.split(",");
+						if(sizeArr.length >= 2) {
+							int size = Integer.parseInt(sizeArr[0]);
+							int maxLength = Integer.parseInt(sizeArr[1]);
+							response.setSize(size);
+							response.setMaxLength(maxLength);
+						}	
+					}
+					
+					//fibBuilder.getResponses().add(response);
+					 
+					 */
+				}
+			}
+		} catch (NumberFormatException e) {
+			log.warn("Cannot parse point for: " + parts[0] + " / " + parts[1], e);
+		}
+	}
+	
+	private float parseFloat(String value, float defaultValue) {
+		float floatValue = defaultValue;
+		
+		if(value != null) {
+			if(value.indexOf(",") >= 0) {
+				value = value.replace(",", ".");
+			}
+			floatValue = Float.parseFloat(value);
+		}
+		return floatValue;
+	}
+}
diff --git a/src/main/java/org/olat/ims/qti21/questionimport/ImportOptions.java b/src/main/java/org/olat/ims/qti21/questionimport/ImportOptions.java
new file mode 100644
index 00000000000..2f13ee140d8
--- /dev/null
+++ b/src/main/java/org/olat/ims/qti21/questionimport/ImportOptions.java
@@ -0,0 +1,43 @@
+/**
+ * <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.questionimport;
+
+/**
+ * 
+ * Initial date: 16.02.2016<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class ImportOptions {
+	
+	private boolean shuffle;
+	
+	public ImportOptions() {
+		//
+	}
+
+	public boolean isShuffle() {
+		return shuffle;
+	}
+
+	public void setShuffle(boolean shuffle) {
+		this.shuffle = shuffle;
+	}
+}
diff --git a/src/main/java/org/olat/ims/qti21/questionimport/OverviewQuestionController.java b/src/main/java/org/olat/ims/qti21/questionimport/OverviewQuestionController.java
new file mode 100644
index 00000000000..6307000793b
--- /dev/null
+++ b/src/main/java/org/olat/ims/qti21/questionimport/OverviewQuestionController.java
@@ -0,0 +1,150 @@
+/**
+ * <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.questionimport;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.impl.Form;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.BooleanCellRenderer;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.CSSIconFlexiCellRenderer;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTableDataModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiColumnModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.generic.wizard.StepFormBasicController;
+import org.olat.core.gui.control.generic.wizard.StepsEvent;
+import org.olat.core.gui.control.generic.wizard.StepsRunContext;
+import org.olat.ims.qti21.model.xml.AssessmentItemBuilder;
+import org.olat.ims.qti21.model.xml.ScoreBuilder;
+
+/**
+ * 
+ * Initial date: 16.02.2016<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class OverviewQuestionController extends StepFormBasicController {
+	
+	private final boolean lastStep;
+	private final AssessmentItemsPackage importedItems;
+	
+	public OverviewQuestionController(UserRequest ureq, WindowControl wControl, StepsRunContext runContext,
+			Form rootForm, AssessmentItemsPackage importedItems, boolean lastStep) {
+		super(ureq, wControl, rootForm, runContext, LAYOUT_VERTICAL, null);
+		this.lastStep = lastStep;
+		this.importedItems = importedItems;
+		initForm(ureq);
+	}
+	
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel();
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(true, Cols.hasError.i18n(), Cols.hasError.ordinal(),
+				false, null, FlexiColumnModel.ALIGNMENT_LEFT,
+				new BooleanCellRenderer(
+						new CSSIconFlexiCellRenderer("o_icon_failed"),
+						new CSSIconFlexiCellRenderer("o_icon_accept"))
+		));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Cols.type.i18n(), Cols.type.ordinal()));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Cols.title.i18n(), Cols.title.ordinal()));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Cols.points.i18n(), Cols.points.ordinal()));
+		
+		ItemsTableDataModel model = new ItemsTableDataModel(importedItems.getItems(), columnsModel);
+		uifactory.addTableElement(getWindowControl(), "overviewTable", model, getTranslator(), formLayout);
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		if(lastStep) {
+			fireEvent(ureq, StepsEvent.INFORM_FINISHED);
+		} else {
+			fireEvent(ureq, StepsEvent.ACTIVATE_NEXT);
+		}
+	}
+	
+	private class ItemsTableDataModel extends DefaultFlexiTableDataModel<AssessmentItemAndMetadata> {
+		private FlexiTableColumnModel columnModel;
+		
+		public ItemsTableDataModel(List<AssessmentItemAndMetadata> options, FlexiTableColumnModel columnModel) {
+			super(options, columnModel);
+		}
+
+		@Override
+		public Object getValueAt(int row, int col) {
+			AssessmentItemAndMetadata importedItem = getObject(row);
+			AssessmentItemBuilder itemBuilder = importedItem.getItemBuilder();
+			switch(Cols.values()[col]) {
+				case hasError: return importedItem.isHasError();
+				case type: {
+					String typeLabel;
+					switch(itemBuilder.getQuestionType()) {
+						case sc: typeLabel = translate("item.type.sc"); break;
+						case mc: typeLabel = translate("item.type.mc"); break;
+						case fib: typeLabel = translate("item.type.fib"); break;
+						default: { typeLabel = "??"; }
+					}
+					return typeLabel;
+				}
+				case title: return itemBuilder.getTitle();
+				case points: {
+					ScoreBuilder score = itemBuilder.getMaxScoreBuilder();
+					if(score == null) {
+						return null;
+					}
+					return score.getScore();
+				}
+				default: return itemBuilder.getAssessmentItem();
+			}
+		}
+
+		@Override
+		public ItemsTableDataModel createCopyWithEmptyList() {
+			return new ItemsTableDataModel(new ArrayList<AssessmentItemAndMetadata>(), columnModel);
+		}
+	}
+	
+	public static enum Cols {
+		hasError("table.header.status"),
+		type("table.header.type"),
+		title("table.header.title"),
+		points("table.header.points");
+		
+		private final String i18n;
+		
+		private Cols(String i18n) {
+			this.i18n = i18n;
+		}
+		
+		public String i18n() {
+			return i18n;
+		}
+	}
+}
diff --git a/src/main/java/org/olat/ims/qti21/questionimport/QImport_1_InputStep.java b/src/main/java/org/olat/ims/qti21/questionimport/QImport_1_InputStep.java
new file mode 100644
index 00000000000..43ee405830e
--- /dev/null
+++ b/src/main/java/org/olat/ims/qti21/questionimport/QImport_1_InputStep.java
@@ -0,0 +1,59 @@
+/**
+ * <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.questionimport;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.impl.Form;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.generic.wizard.BasicStep;
+import org.olat.core.gui.control.generic.wizard.PrevNextFinishConfig;
+import org.olat.core.gui.control.generic.wizard.Step;
+import org.olat.core.gui.control.generic.wizard.StepFormController;
+import org.olat.core.gui.control.generic.wizard.StepsRunContext;
+
+/**
+ * 
+ * Initial date: 16.02.2016<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class QImport_1_InputStep extends BasicStep {
+	
+	private final ImportOptions options;
+	private final AssessmentItemsPackage importedItems;
+	
+	public QImport_1_InputStep(UserRequest ureq, AssessmentItemsPackage importedItems, ImportOptions options, Step additionalStep) {
+		super(ureq);
+		this.options = options;
+		this.importedItems = importedItems;
+		setNextStep(new QImport_2_OverviewStep(ureq, importedItems, additionalStep));
+		setI18nTitleAndDescr("wizard.import.input.title", "wizard.import.input.title");
+	}
+
+	@Override
+	public PrevNextFinishConfig getInitialPrevNextFinishConfig() {
+		return new PrevNextFinishConfig(false, true, false);
+	}
+
+	@Override
+	public StepFormController getStepController(UserRequest ureq, WindowControl wControl, StepsRunContext runContext, Form form) {
+		return new TextInputController(ureq, wControl, runContext, form, importedItems, options);
+	}
+}
diff --git a/src/main/java/org/olat/ims/qti21/questionimport/QImport_2_OverviewStep.java b/src/main/java/org/olat/ims/qti21/questionimport/QImport_2_OverviewStep.java
new file mode 100644
index 00000000000..cdad9400e01
--- /dev/null
+++ b/src/main/java/org/olat/ims/qti21/questionimport/QImport_2_OverviewStep.java
@@ -0,0 +1,64 @@
+/**
+ * <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.questionimport;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.impl.Form;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.generic.wizard.BasicStep;
+import org.olat.core.gui.control.generic.wizard.PrevNextFinishConfig;
+import org.olat.core.gui.control.generic.wizard.Step;
+import org.olat.core.gui.control.generic.wizard.StepFormController;
+import org.olat.core.gui.control.generic.wizard.StepsRunContext;
+
+/**
+ * 
+ * Initial date: 16.02.2016<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class QImport_2_OverviewStep extends BasicStep {
+	
+	private final boolean nextStep;
+	private final AssessmentItemsPackage importedItems;
+	
+	public QImport_2_OverviewStep(UserRequest ureq, AssessmentItemsPackage importedItems, Step additionalStep) {
+		super(ureq);
+		this.importedItems = importedItems;
+		if(additionalStep == null) {
+			setNextStep(NOSTEP);
+			nextStep = false;
+		} else {
+			setNextStep(additionalStep);
+			nextStep = true;
+		}
+		setI18nTitleAndDescr("wizard.import.overview.title", "wizard.import.overview.title");
+	}
+
+	@Override
+	public PrevNextFinishConfig getInitialPrevNextFinishConfig() {
+		return new PrevNextFinishConfig(true, nextStep, !nextStep);
+	}
+
+	@Override
+	public StepFormController getStepController(UserRequest ureq, WindowControl wControl, StepsRunContext runContext, Form form) {
+		return new OverviewQuestionController(ureq, wControl, runContext, form, importedItems, !nextStep);
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/ims/qti21/questionimport/TextInputController.java b/src/main/java/org/olat/ims/qti21/questionimport/TextInputController.java
new file mode 100644
index 00000000000..684c557adad
--- /dev/null
+++ b/src/main/java/org/olat/ims/qti21/questionimport/TextInputController.java
@@ -0,0 +1,123 @@
+/**
+ * <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.questionimport;
+
+import java.io.InputStream;
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.olat.core.dispatcher.mapper.Mapper;
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.elements.TextElement;
+import org.olat.core.gui.components.form.flexible.impl.Form;
+import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.generic.wizard.StepFormBasicController;
+import org.olat.core.gui.control.generic.wizard.StepsEvent;
+import org.olat.core.gui.control.generic.wizard.StepsRunContext;
+import org.olat.core.gui.media.MediaResource;
+import org.olat.core.gui.media.StreamedMediaResource;
+import org.olat.ims.qti21.QTI21Service;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 16.02.2016<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class TextInputController extends StepFormBasicController {
+
+	private String validatedInp;
+	private TextElement inputElement;
+	
+	private List<AssessmentItemAndMetadata> parsedItems;
+	private final AssessmentItemsPackage importedItems;
+	private final ImportOptions options;
+	
+	@Autowired
+	private QTI21Service qtiService;
+	
+	public TextInputController(UserRequest ureq, WindowControl wControl, StepsRunContext runContext, Form rootForm,
+			AssessmentItemsPackage importedItems, ImportOptions options) {
+		super(ureq, wControl, rootForm, runContext, LAYOUT_VERTICAL, null);
+		this.importedItems = importedItems;
+		this.options = options;
+		initForm(ureq);
+	}
+	
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		setFormDescription("wizard.import.input.description");
+		setFormContextHelp("Data Management#qb_import");
+		
+		FormLayoutContainer textContainer = FormLayoutContainer.createCustomFormLayout("index", getTranslator(), velocity_root + "/example.html");
+		formLayout.add(textContainer);
+		String mapperURI = registerMapper(ureq, new ExampleMapper());
+		textContainer.contextPut("mapperURI", mapperURI);
+		
+		inputElement = uifactory.addTextAreaElement("importform", "form.importdata", -1, 10, 100, false, "", formLayout);
+		inputElement.setMandatory(true);
+		inputElement.setNotEmptyCheck("form.legende.mandatory");
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected boolean validateFormLogic(UserRequest ureq) {
+		boolean allOk = true;
+		
+		String inp = inputElement.getValue();
+		if(validatedInp == null || !validatedInp.equals(inp)) {
+			boolean errors = convertInputField();
+			allOk &= !errors;
+		}
+		
+		return allOk & super.validateFormLogic(ureq);
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		importedItems.setItems(parsedItems);
+		fireEvent(ureq, StepsEvent.ACTIVATE_NEXT);
+	}
+	
+	private boolean convertInputField() {
+		boolean importDataError = false;
+		CSVToQuestionConverter converter = new CSVToQuestionConverter(options, qtiService.qtiSerializer());
+		converter.parse(inputElement.getValue());
+		parsedItems = converter.getItems();
+		return importDataError;
+	}
+	
+	private static class ExampleMapper implements Mapper {
+		@Override
+		public MediaResource handle(String relPath, HttpServletRequest request) {
+			InputStream in = TextInputController.class.getResourceAsStream("qti-import-metadata.xlsx");
+			return new StreamedMediaResource(in, "ImportExample.xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/ims/qti21/questionimport/_content/example.html b/src/main/java/org/olat/ims/qti21/questionimport/_content/example.html
new file mode 100644
index 00000000000..618a6423b6f
--- /dev/null
+++ b/src/main/java/org/olat/ims/qti21/questionimport/_content/example.html
@@ -0,0 +1,4 @@
+<a href="$mapperURI/ImportSample.xlsx" target="_blank">
+	<i class="o_icon o_filetype_xls o_icon-lg"></i> 
+	$r.translate("download.example")
+</a>
\ No newline at end of file
diff --git a/src/main/java/org/olat/ims/qti21/questionimport/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/ims/qti21/questionimport/_i18n/LocalStrings_de.properties
new file mode 100644
index 00000000000..6fe1d1cf71e
--- /dev/null
+++ b/src/main/java/org/olat/ims/qti21/questionimport/_i18n/LocalStrings_de.properties
@@ -0,0 +1 @@
+#Mon Aug 25 16:10:51 CEST 2014
\ No newline at end of file
diff --git a/src/main/java/org/olat/ims/qti21/questionimport/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/ims/qti21/questionimport/_i18n/LocalStrings_en.properties
new file mode 100644
index 00000000000..3709260284b
--- /dev/null
+++ b/src/main/java/org/olat/ims/qti21/questionimport/_i18n/LocalStrings_en.properties
@@ -0,0 +1,14 @@
+#Mon Aug 25 16:10:51 CEST 2014
+download.example=Template excel import
+wizard.import.input.title=Data input
+wizard.import.input.description=Import the questions you created based on the Excel template below via copy&paste. Select the first three columns in the Excel file and copy them to the data input field.
+wizard.import.overview.title=Overview
+input.title=Data input
+form.importdata=Copied columns from Excel (comma separated)
+table.header.status=Status
+table.header.type=Question type
+table.header.title=Title
+table.header.points=Points
+item.type.fib=$org.olat.ims.qti.editor\:item.type.fib
+item.type.mc=$org.olat.ims.qti.editor\:item.type.mc
+item.type.sc=$org.olat.ims.qti.editor\:item.type.sc
\ No newline at end of file
diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentTestComposerController.java b/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentTestComposerController.java
index 42fd6416c3f..ccba8473261 100644
--- a/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentTestComposerController.java
+++ b/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentTestComposerController.java
@@ -46,6 +46,10 @@ import org.olat.core.gui.control.VetoableCloseController;
 import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.controller.MainLayoutBasicController;
 import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController;
+import org.olat.core.gui.control.generic.wizard.Step;
+import org.olat.core.gui.control.generic.wizard.StepRunnerCallback;
+import org.olat.core.gui.control.generic.wizard.StepsMainRunController;
+import org.olat.core.gui.control.generic.wizard.StepsRunContext;
 import org.olat.core.util.Util;
 import org.olat.fileresource.FileResourceManager;
 import org.olat.ims.qti21.QTI21Constants;
@@ -58,6 +62,10 @@ import org.olat.ims.qti21.model.xml.interactions.KPrimAssessmentItemBuilder;
 import org.olat.ims.qti21.model.xml.interactions.MultipleChoiceAssessmentItemBuilder;
 import org.olat.ims.qti21.model.xml.interactions.SingleChoiceAssessmentItemBuilder;
 import org.olat.ims.qti21.pool.QTI21QPoolServiceProvider;
+import org.olat.ims.qti21.questionimport.AssessmentItemAndMetadata;
+import org.olat.ims.qti21.questionimport.AssessmentItemsPackage;
+import org.olat.ims.qti21.questionimport.ImportOptions;
+import org.olat.ims.qti21.questionimport.QImport_1_InputStep;
 import org.olat.ims.qti21.ui.AssessmentTestDisplayController;
 import org.olat.ims.qti21.ui.editor.events.AssessmentItemEvent;
 import org.olat.ims.qti21.ui.editor.events.AssessmentSectionEvent;
@@ -103,6 +111,7 @@ public class AssessmentTestComposerController extends MainLayoutBasicController
 	private Controller currentEditorCtrl;
 	private CloseableModalController cmc;
 	private SelectItemController selectQItemCtrl;
+	private StepsMainRunController importTableWizard;
 	private final LayoutMain3ColsController columnLayoutCtr;
 	
 	private final File unzippedDirRoot;
@@ -110,6 +119,7 @@ public class AssessmentTestComposerController extends MainLayoutBasicController
 	private ManifestType manifest;
 	private ResolvedAssessmentTest resolvedAssessmentTest;
 	
+	private final boolean survey = false;
 	private final boolean restrictedEdit;
 	
 	@Autowired
@@ -244,7 +254,14 @@ public class AssessmentTestComposerController extends MainLayoutBasicController
 			if(event instanceof QItemViewEvent) {
 				QItemViewEvent e = (QItemViewEvent)event;
 				List<QuestionItemView> items = e.getItemList();
-				doInsert(items);
+				doInsert(ureq, items);
+			}
+		}  else if(importTableWizard == source) {
+			AssessmentItemsPackage importPackage = (AssessmentItemsPackage)importTableWizard.getRunContext().get("importPackage");
+			getWindowControl().pop();
+			cleanUp();
+			if(event == Event.DONE_EVENT || event == Event.CHANGED_EVENT) {
+				doInsert(ureq, importPackage);
 			}
 		} else if(cmc == source) {
 			cleanUp();
@@ -285,6 +302,8 @@ public class AssessmentTestComposerController extends MainLayoutBasicController
 			doNewAssessmentItem(ureq, menuTree.getSelectedNode(), new EssayAssessmentItemBuilder(qtiService.qtiSerializer()));
 		} else if(importFromPoolLink == source) {
 			doSelectQItem(ureq);
+		} else if(importFromTableLink == source) {
+			doImportTable(ureq);
 		}
 	}
 	
@@ -300,35 +319,40 @@ public class AssessmentTestComposerController extends MainLayoutBasicController
 		listenTo(cmc);
 	}
 	
-	private void doInsert(List<QuestionItemView> items) {
+	private void doImportTable(UserRequest ureq) {
+		removeAsListenerAndDispose(importTableWizard);
+
+		final AssessmentItemsPackage importPackage = new AssessmentItemsPackage();
+		final ImportOptions options = new ImportOptions();
+		options.setShuffle(!survey);
+		Step start = new QImport_1_InputStep(ureq, importPackage, options, null);
+		StepRunnerCallback finish = new StepRunnerCallback() {
+			@Override
+			public Step execute(UserRequest uureq, WindowControl wControl, StepsRunContext runContext) {
+				runContext.put("importPackage", importPackage);
+				return StepsMainRunController.DONE_MODIFIED;
+			}
+		};
+		
+		importTableWizard = new StepsMainRunController(ureq, getWindowControl(), start, finish, null,
+				translate("tools.import.table"), "o_mi_table_import_wizard");
+		listenTo(importTableWizard);
+		getWindowControl().pushAsModalDialog(importTableWizard.getInitialComponent());
+	}
+	
+	private void doInsert(UserRequest ureq, List<QuestionItemView> items) {
 		TreeNode selectedNode = menuTree.getSelectedNode();
 		TreeNode sectionNode = getNearestSection(selectedNode);
 		
 		String firstItemId = null;
-		
 		try {
+			AssessmentSection section = (AssessmentSection)sectionNode.getUserObject();
 			for(QuestionItemView item:items) {
 				AssessmentItem assessmentItem = qti21QPoolServiceProvider.exportToQTIEditor(item, unzippedDirRoot);
-				AssessmentSection section = (AssessmentSection)sectionNode.getUserObject();
-				
-				AssessmentItemRef itemRef = new AssessmentItemRef(section);
-				String itemId = assessmentItem.getIdentifier();
+				String itemId = doInsert(section, assessmentItem);
 				if(firstItemId == null) {
 					firstItemId = itemId;
 				}
-				itemRef.setIdentifier(Identifier.parseString(itemId));
-				File itemFile = new File(unzippedDirRoot, itemId + ".xml");
-				itemRef.setHref(new URI(itemFile.getName()));
-				section.getSectionParts().add(itemRef);
-				
-				qtiService.persistAssessmentObject(itemFile, assessmentItem);
-				
-				URI testUri = resolvedAssessmentTest.getTestLookup().getSystemId();
-				File testFile = new File(testUri);
-				qtiService.updateAssesmentObject(testFile, resolvedAssessmentTest);
-
-				ManifestPackage.appendAssessmentItem(itemFile.getName(), manifest);
-				ManifestPackage.write(manifest, new File(unzippedDirRoot, "imsmanifest.xml"));
 			}
 		} catch (IOException | URISyntaxException e) {
 			showError("error.import.question");
@@ -340,6 +364,56 @@ public class AssessmentTestComposerController extends MainLayoutBasicController
 		TreeNode newItemNode = menuTree.getTreeModel().getNodeById(firstItemId);
 		menuTree.setSelectedNode(newItemNode);
 		menuTree.open(newItemNode);
+		partEditorFactory(ureq, newItemNode);
+	}
+
+	private void doInsert(UserRequest ureq, AssessmentItemsPackage importPackage) {
+		TreeNode selectedNode = menuTree.getSelectedNode();
+		TreeNode sectionNode = getNearestSection(selectedNode);
+		
+		String firstItemId = null;
+		try {
+			AssessmentSection section = (AssessmentSection)sectionNode.getUserObject();
+			
+			List<AssessmentItemAndMetadata> itemsAndMetadata = importPackage.getItems();
+			for(AssessmentItemAndMetadata itemAndMetadata:itemsAndMetadata) {
+				AssessmentItem assessmentItem = itemAndMetadata.getItemBuilder().getAssessmentItem();
+				String itemId = doInsert(section, assessmentItem);
+				if(firstItemId == null) {
+					firstItemId = itemId;
+				}
+			}
+		} catch (URISyntaxException e) {
+			showError("error.import.question");
+			logError("", e);
+		}
+		
+		updateTreeModel();
+		
+		TreeNode newItemNode = menuTree.getTreeModel().getNodeById(firstItemId);
+		menuTree.setSelectedNode(newItemNode);
+		menuTree.open(newItemNode);
+		partEditorFactory(ureq, newItemNode);
+	}
+	
+	private String doInsert(AssessmentSection section, AssessmentItem assessmentItem)
+	throws URISyntaxException {
+		AssessmentItemRef itemRef = new AssessmentItemRef(section);
+		String itemId = assessmentItem.getIdentifier();
+		itemRef.setIdentifier(Identifier.parseString(itemId));
+		File itemFile = new File(unzippedDirRoot, itemId + ".xml");
+		itemRef.setHref(new URI(itemFile.getName()));
+		section.getSectionParts().add(itemRef);
+		
+		qtiService.persistAssessmentObject(itemFile, assessmentItem);
+		
+		URI testUri = resolvedAssessmentTest.getTestLookup().getSystemId();
+		File testFile = new File(testUri);
+		qtiService.updateAssesmentObject(testFile, resolvedAssessmentTest);
+
+		ManifestPackage.appendAssessmentItem(itemFile.getName(), manifest);
+		ManifestPackage.write(manifest, new File(unzippedDirRoot, "imsmanifest.xml"));
+		return itemId;
 	}
 	
 	private TreeNode doOpenFirstItem() {
diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/SingleChoiceEditorController.java b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/SingleChoiceEditorController.java
index 7925d6d10f6..c48fdf8e328 100644
--- a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/SingleChoiceEditorController.java
+++ b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/SingleChoiceEditorController.java
@@ -235,7 +235,7 @@ public class SingleChoiceEditorController extends FormBasicController {
 	
 	private void doAddSimpleChoice(UserRequest ureq) {
 		ChoiceInteraction interaction = itemBuilder.getChoiceInteraction();
-		SimpleChoice newChoice = AssessmentItemFactory.createSimpleChoice(interaction, "sc");
+		SimpleChoice newChoice = AssessmentItemFactory.createSimpleChoice(interaction, "New answer", "sc");
 		wrapAnswer(ureq, newChoice);
 		flc.setDirty(true);
 	}
diff --git a/src/test/java/org/olat/ims/qti21/model/xml/AssessmentItemPackageTest.java b/src/test/java/org/olat/ims/qti21/model/xml/AssessmentItemPackageTest.java
index 282619e567d..09eb1ae61b2 100644
--- a/src/test/java/org/olat/ims/qti21/model/xml/AssessmentItemPackageTest.java
+++ b/src/test/java/org/olat/ims/qti21/model/xml/AssessmentItemPackageTest.java
@@ -111,7 +111,7 @@ public class AssessmentItemPackageTest {
 	}
 
 
-	protected AssessmentItem createAssessmentItem() {
+	public AssessmentItem createAssessmentItem() {
 		AssessmentItem assessmentItem = new AssessmentItem();
 		assessmentItem.setIdentifier("id" + UUID.randomUUID());
 		assessmentItem.setTitle("Physicists");
-- 
GitLab