diff --git a/Management.Test/Markdown/QuizMarkdownTests.cs b/Management.Test/Markdown/QuizMarkdownTests.cs
index 6f0e309..1abb057 100644
--- a/Management.Test/Markdown/QuizMarkdownTests.cs
+++ b/Management.Test/Markdown/QuizMarkdownTests.cs
@@ -279,4 +279,56 @@ b) false
secondQuestion.Points.Should().Be(2);
secondQuestion.QuestionType.Should().Be(QuestionType.MULTIPLE_CHOICE);
}
+ [Test]
+ public void CanParseEssay()
+ {
+ var rawMarkdownQuiz = @"
+Name: Test Quiz
+LockAtDueDate: true
+ShuffleAnswers: true
+OneQuestionAtATime: false
+DueAt: 2023-08-21T23:59:00
+LockAt: 2023-08-21T23:59:00
+AssignmentGroup: Assignments
+AllowedAttempts: -1
+Description: this is the
+multi line
+description
+---
+Which events are triggered when the user clicks on an input field?
+essay
+";
+
+ var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz);
+ var firstQuestion = quiz.Questions.First();
+ firstQuestion.Points.Should().Be(1);
+ firstQuestion.QuestionType.Should().Be(QuestionType.ESSAY);
+ firstQuestion.Text.Should().NotContain("essay");
+ }
+ [Test]
+ public void CanParseShortAnswer()
+ {
+ var rawMarkdownQuiz = @"
+Name: Test Quiz
+LockAtDueDate: true
+ShuffleAnswers: true
+OneQuestionAtATime: false
+DueAt: 2023-08-21T23:59:00
+LockAt: 2023-08-21T23:59:00
+AssignmentGroup: Assignments
+AllowedAttempts: -1
+Description: this is the
+multi line
+description
+---
+Which events are triggered when the user clicks on an input field?
+short answer
+";
+
+ var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz);
+ var firstQuestion = quiz.Questions.First();
+ firstQuestion.Points.Should().Be(1);
+ firstQuestion.QuestionType.Should().Be(QuestionType.SHORT_ANSWER);
+ firstQuestion.Text.Should().NotContain("short answer");
+ }
}
\ No newline at end of file
diff --git a/Management.Web/Shared/Components/Quiz/Markdown/MarkdownQuestionPreview.razor b/Management.Web/Shared/Components/Quiz/Markdown/MarkdownQuestionPreview.razor
index 0e55528..e98a6af 100644
--- a/Management.Web/Shared/Components/Quiz/Markdown/MarkdownQuestionPreview.razor
+++ b/Management.Web/Shared/Components/Quiz/Markdown/MarkdownQuestionPreview.razor
@@ -6,40 +6,53 @@
}
-@((MarkupString)Question.HtmlText)
+
+
+ @((MarkupString)Question.HtmlText)
+
+
+ @Question.QuestionType
+
+
@foreach(var answer in Question.Answers)
{
+ string answerPreview = answer.HtmlText.StartsWith("
-
- @if(answer.Correct)
- {
-
- }
- else
- {
-
- }
-
-
- @((MarkupString)answer.HtmlText)
+ @if(answer.Correct)
+ {
+
+ }
+ else
+ {
+
+ @if(Question.QuestionType == QuestionType.MULTIPLE_ANSWERS)
+ {
+ [ ]
+ }
+
+ }
+
+ @((MarkupString)answerPreview)
}
\ No newline at end of file
diff --git a/Management.Web/Shared/Components/Quiz/Markdown/MarkdownQuizForm.razor b/Management.Web/Shared/Components/Quiz/Markdown/MarkdownQuizForm.razor
index ed3e796..2376a05 100644
--- a/Management.Web/Shared/Components/Quiz/Markdown/MarkdownQuizForm.razor
+++ b/Management.Web/Shared/Components/Quiz/Markdown/MarkdownQuizForm.razor
@@ -23,7 +23,7 @@
error = null;
testQuiz = newQuiz;
}
- catch(Exception e)
+ catch(QuizMarkdownParseException e)
{
error = e.Message;
}
@@ -38,8 +38,6 @@
{
if (quizContext.Quiz != null)
{
- Console.WriteLine("reloading quiz editor");
-
if(quizMarkdownInput == "")
{
quizMarkdownInput = quizContext.Quiz.ToMarkdown();
@@ -66,7 +64,7 @@
private void onHide()
{
- quizMarkdownInput = "";
+ _quizMarkdownInput = "";
quizContext.Quiz = null;
}
}
@@ -95,7 +93,7 @@
@if(error != null)
{
-
@error
+
@error
}
diff --git a/Management.Web/Shared/Components/Quiz/Markdown/QuizPreview.razor b/Management.Web/Shared/Components/Quiz/Markdown/QuizPreview.razor
index 9b41bd8..13cfebf 100644
--- a/Management.Web/Shared/Components/Quiz/Markdown/QuizPreview.razor
+++ b/Management.Web/Shared/Components/Quiz/Markdown/QuizPreview.razor
@@ -13,7 +13,6 @@
}
private void reload()
{
- Console.WriteLine(JsonSerializer.Serialize(quizContext.Quiz));
this.InvokeAsync(this.StateHasChanged);
}
public void Dispose()
@@ -38,7 +37,7 @@
@foreach(var question in Quiz.Questions)
{
-
+
!string.IsNullOrWhiteSpace(str))
- .Select(q => LocalQuizQuestion.ParseMarkdown(q))
+ .Select((q, i) => LocalQuizQuestion.ParseMarkdown(q, i))
.ToArray();
return quizWithoutQuestions with
{
@@ -128,3 +128,11 @@ Description: {Description}
return string.Empty;
}
}
+
+public class QuizMarkdownParseException : Exception
+{
+ public QuizMarkdownParseException(string message): base(message)
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/Management/Models/Local/LocalQuizQuestion.cs b/Management/Models/Local/LocalQuizQuestion.cs
index 72de689..a13f936 100644
--- a/Management/Models/Local/LocalQuizQuestion.cs
+++ b/Management/Models/Local/LocalQuizQuestion.cs
@@ -1,3 +1,4 @@
+using System.Security.Cryptography;
using System.Text.RegularExpressions;
namespace LocalModels;
@@ -36,21 +37,47 @@ public record LocalQuizQuestion
}
private static readonly string[] validFirstAnswerDelimiters = new string[] { "*a)", "a)", "[ ]", "[*]" };
- public static LocalQuizQuestion ParseMarkdown(string input)
+
+ public static LocalQuizQuestion ParseMarkdown(string input, int questionIndex)
{
- var lines = input.Split(Environment.NewLine);
+ var lines = input.Trim().Split(Environment.NewLine);
var firstLineIsPoints = lines.First().Contains("points: ", StringComparison.CurrentCultureIgnoreCase);
- int points = firstLineIsPoints ? int.Parse(lines.First().Split(": ")[1]) : 1;
+
+ var textHasPoints = lines.Length > 0
+ && lines.First().Contains(": ")
+ && lines.First().Split(": ").Length > 1
+ && int.TryParse(lines.First().Split(": ")[1], out _);
+
+ int points = firstLineIsPoints && textHasPoints ? int.Parse(lines.First().Split(": ")[1]) : 1;
var linesWithoutPoints = firstLineIsPoints ? lines[1..] : lines;
var linesWithoutAnswers = linesWithoutPoints
- .TakeWhile(line => !validFirstAnswerDelimiters.Any(prefix => line.TrimStart().StartsWith(prefix)))
+ .TakeWhile(
+ (line, index) =>
+ !validFirstAnswerDelimiters.Any(prefix => line.TrimStart().StartsWith(prefix))
+ )
.ToArray();
- var description = string.Join(Environment.NewLine, linesWithoutAnswers);
- var (answers, questionType) = getAnswers(linesWithoutPoints);
+ var questionType = getQuestionType(linesWithoutPoints, questionIndex);
+
+ var questionTypesWithoutAnswers = new string[] { "essay", "short answer", "short_answer" };
+ var descriptionLines = questionTypesWithoutAnswers.Contains(questionType.ToLower())
+ ? linesWithoutAnswers
+ .TakeWhile(
+ (line, index) => index != linesWithoutPoints.Length && !questionTypesWithoutAnswers.Contains(line.ToLower())
+ )
+ .ToArray()
+ : linesWithoutAnswers;
+ var description = string.Join(Environment.NewLine, descriptionLines);
+
+
+
+ var typesWithAnswers = new string[] { "multiple_choice", "multiple_answers" };
+ var answers = typesWithAnswers.Contains(questionType)
+ ? getAnswers(linesWithoutPoints, questionIndex)
+ : [];
return new LocalQuizQuestion()
{
@@ -61,55 +88,79 @@ public record LocalQuizQuestion
};
}
- private static (LocalQuizQuestionAnswer[], string questionType) getAnswers(string[] linesWithoutPoints)
+ private static string getQuestionType(string[] linesWithoutPoints, int questionIndex)
+ {
+
+ if (linesWithoutPoints.Length == 0)
+ return "";
+ if (linesWithoutPoints[^1].Equals("essay", StringComparison.CurrentCultureIgnoreCase))
+ return "essay";
+ if (linesWithoutPoints[^1].Equals("short answer", StringComparison.CurrentCultureIgnoreCase))
+ return "short_answer";
+ if (linesWithoutPoints[^1].Equals("short_answer", StringComparison.CurrentCultureIgnoreCase))
+ return "short_answer";
+
+ var answerLines = getAnswersGroupedByLines(linesWithoutPoints, questionIndex);
+ var isMultipleChoice =
+ answerLines.First().StartsWith("a)")
+ || answerLines.First().StartsWith("*a)");
+ if (isMultipleChoice)
+ return "multiple_choice";
+
+ var isMultipleAnswer =
+ answerLines.First().StartsWith("[ ]")
+ || answerLines.First().StartsWith("[*]");
+
+ if (isMultipleAnswer)
+ return "multiple_answers";
+
+ return "";
+ }
+
+ private static List getAnswersGroupedByLines(string[] linesWithoutPoints, int questionIndex)
{
var indexOfAnswerStart = linesWithoutPoints
.ToList()
.FindIndex(
l => validFirstAnswerDelimiters.Any(prefix => l.TrimStart().StartsWith(prefix))
);
+ if (indexOfAnswerStart == -1)
+ {
+ var debugLine = linesWithoutPoints.FirstOrDefault(l => l.Trim().Length > 0);
+ throw new QuizMarkdownParseException($"question {questionIndex + 1}: no answers when detecting question type on {debugLine}");
+ }
+
var answerLinesRaw = linesWithoutPoints[indexOfAnswerStart..];
var answerStartPattern = @"^(\*?[a-z]\))|\[\s*\]|\[\*\]";
var answerLines = answerLinesRaw.Aggregate(new List(), (acc, line) =>
{
- if (!Regex.IsMatch(line, answerStartPattern))
+ var isNewAnswer = Regex.IsMatch(line, answerStartPattern);
+ if (isNewAnswer)
{
- if (acc.Count != 0) // Append to the previous line if there is one
- {
- int lastIndex = acc.Count - 1;
- acc[lastIndex] += Environment.NewLine + line;
- }
- else
- {
- acc.Add(line);
- }
+ acc.Add(line);
+ return acc;
}
+
+ if (acc.Count != 0) // Append to the previous line if there is one
+ acc[^1] += Environment.NewLine + line;
else
- {
- acc.Add(line); // Add as a new line if it matches the pattern
- }
+ acc.Add(line);
return acc;
});
+ return answerLines;
+ }
- var answers = answerLines.Select(LocalQuizQuestionAnswer.ParseMarkdown).ToArray();
+ private static LocalQuizQuestionAnswer[] getAnswers(string[] linesWithoutPoints, int questionIndex)
+ {
+ var answerLines = getAnswersGroupedByLines(linesWithoutPoints, questionIndex);
- var isMultipleChoice =
- answerLines.First().StartsWith("a)")
- || answerLines.First().StartsWith("*a)");
+ var answers = answerLines
+ .Select((a, i) => LocalQuizQuestionAnswer.ParseMarkdown(a))
+ .ToArray();
- var isMultipleAnswer =
- answerLines.First().StartsWith("[ ]")
- || answerLines.First().StartsWith("[*]");
-
- var questionType = isMultipleChoice
- ? "multiple_choice"
- : isMultipleAnswer
- ? "multiple_answers"
- : "";
-
- return (answers, questionType);
+ return answers;
}
}