working on markdown quiz experience

This commit is contained in:
2023-10-12 15:08:52 -06:00
parent e5defbc0cf
commit 4aa045d952
6 changed files with 193 additions and 72 deletions

View File

@@ -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<string> 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<string>(), (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;
}
}