From a60151575c0ab7901214d5193dfd9aaeec320a3b Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 3 Oct 2023 17:21:46 -0600 Subject: [PATCH] can parse at least some markdown quiz questions --- Management.Test/Markdown/QuizMarkdownTests.cs | 44 +++++++++++++ Management/Models/Local/LocalQuiz.cs | 16 ++++- Management/Models/Local/LocalQuizQuestion.cs | 65 ++++++++++++++++++- .../Models/Local/LocalQuizQuestionAnswer.cs | 15 +++++ 4 files changed, 138 insertions(+), 2 deletions(-) diff --git a/Management.Test/Markdown/QuizMarkdownTests.cs b/Management.Test/Markdown/QuizMarkdownTests.cs index 7e8dc8f..9c745c3 100644 --- a/Management.Test/Markdown/QuizMarkdownTests.cs +++ b/Management.Test/Markdown/QuizMarkdownTests.cs @@ -161,6 +161,7 @@ AllowedAttempts: -1 Description: this is the multi line description +--- "; var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz); @@ -173,4 +174,47 @@ description multi line description"); } + + [Test] + public void TestCanParseQuizWithQuestions() + { + 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 +--- +Points: 2 +`some type` of question + +with many + +``` +lines +``` + +*a) true +b) false + + endline +--- +"; + + var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz); + var firstQuestion = quiz.Questions.First(); + firstQuestion.Points.Should().Be(2); + firstQuestion.Text.Should().Contain("```"); + firstQuestion.Text.Should().Contain("`some type` of question"); + firstQuestion.Answers.First().Text.Should().Be("true"); + firstQuestion.Answers.First().Correct.Should().BeTrue(); + firstQuestion.Answers.ElementAt(1).Correct.Should().BeFalse(); + firstQuestion.Answers.ElementAt(1).Text.Should().Contain("endline"); + } } \ No newline at end of file diff --git a/Management/Models/Local/LocalQuiz.cs b/Management/Models/Local/LocalQuiz.cs index a0c3115..63417bb 100644 --- a/Management/Models/Local/LocalQuiz.cs +++ b/Management/Models/Local/LocalQuiz.cs @@ -61,9 +61,22 @@ Description: {Description} public static LocalQuiz ParseMarkdown(string input) { - var splitInput = input.Split(Environment.NewLine + Environment.NewLine); + var splitInput = input.Split("---" + Environment.NewLine); var settings = splitInput[0]; + var quizWithoutQuestions = getQuizWithOnlySettings(settings); + var questions = splitInput[1..] + .Where(str => !string.IsNullOrWhiteSpace(str)) + .Select(q => LocalQuizQuestion.ParseMarkdown(q)) + .ToArray(); + return quizWithoutQuestions with + { + Questions = questions + }; + } + + private static LocalQuiz getQuizWithOnlySettings(string settings) + { var name = extractLabelValue(settings, "Name"); var lockAtDueDate = bool.Parse(extractLabelValue(settings, "LockAtDueDate")); var shuffleAnswers = bool.Parse(extractLabelValue(settings, "ShuffleAnswers")); @@ -89,6 +102,7 @@ Description: {Description} Questions = new LocalQuizQuestion[] { } }; } + static string extractLabelValue(string input, string label) { string pattern = $@"{label}: (.*?)\n"; diff --git a/Management/Models/Local/LocalQuizQuestion.cs b/Management/Models/Local/LocalQuizQuestion.cs index 6ec578d..f597351 100644 --- a/Management/Models/Local/LocalQuizQuestion.cs +++ b/Management/Models/Local/LocalQuizQuestion.cs @@ -1,3 +1,5 @@ +using System.Text.RegularExpressions; + namespace LocalModels; public record LocalQuizQuestion @@ -21,7 +23,7 @@ public record LocalQuizQuestion var questionTypeIndicator = isMultipleChoice - ? $"{correctIndicator}{questionLetter}) " + ? $"{correctIndicator}{questionLetter}) " : $"[{correctIndicator}] "; var textWithSpecificNewline = answer.Text.Replace(Environment.NewLine, Environment.NewLine + " "); @@ -35,6 +37,67 @@ public record LocalQuizQuestion --- "; } + + private static readonly string[] validFirstAnswerDelimiters = new string[] { "*a)", "a)", "[ ]", "[*]" }; + public static LocalQuizQuestion ParseMarkdown(string input) + { + var lines = input.Split(Environment.NewLine); + var firstLineIsPoints = lines.First().Contains("points: ", StringComparison.CurrentCultureIgnoreCase); + int points = firstLineIsPoints ? 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))) + .ToArray(); + var description = string.Join(Environment.NewLine, linesWithoutAnswers); + + + LocalQuizQuestionAnswer[] answers = getAnswers(linesWithoutPoints); + + return new LocalQuizQuestion() + { + Text = description, + Points = points, + Answers = answers + }; + } + + private static LocalQuizQuestionAnswer[] getAnswers(string[] linesWithoutPoints) + { + var indexOfAnswerStart = linesWithoutPoints + .ToList() + .FindIndex( + l => validFirstAnswerDelimiters.Any(prefix => l.TrimStart().StartsWith(prefix)) + ); + var answerLinesRaw = linesWithoutPoints[indexOfAnswerStart..]; + + var answerStartPattern = @"^(\*?[a-z]\))|\[\s*\]|\[\*\]"; + var answerLines = answerLinesRaw.Aggregate(new List(), (acc, line) => + { + if (!Regex.IsMatch(line, answerStartPattern)) + { + 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); + } + } + else + { + acc.Add(line); // Add as a new line if it matches the pattern + } + + return acc; + }); + + var answers = answerLines.Select(LocalQuizQuestionAnswer.ParseMarkdown).ToArray(); + return answers; + } } public static class QuestionType diff --git a/Management/Models/Local/LocalQuizQuestionAnswer.cs b/Management/Models/Local/LocalQuizQuestionAnswer.cs index c0d05ec..e40137f 100644 --- a/Management/Models/Local/LocalQuizQuestionAnswer.cs +++ b/Management/Models/Local/LocalQuizQuestionAnswer.cs @@ -1,3 +1,5 @@ +using System.Text.RegularExpressions; + namespace LocalModels; public record LocalQuizQuestionAnswer @@ -11,4 +13,17 @@ public record LocalQuizQuestionAnswer public string HtmlText => Markdig.Markdown.ToHtml(Text); + public static LocalQuizQuestionAnswer ParseMarkdown(string input) + { + var isCorrect = input[0] == '*' || input[1] == '*'; + string startingQuestionPattern = @"^(?:\*[a-z]\))|\[\s*\]|\[\*\] "; + var text = Regex.Replace(input, startingQuestionPattern, string.Empty).Trim(); + + return new LocalQuizQuestionAnswer() + { + Correct = isCorrect, + Text=text, + }; + } + }