using Entities.DTO; using System.Text.Json.Serialization; using System.Text.Json; using Entities.Contracts; using Microsoft.Extensions.Options; namespace TechHelper.Client.Exam { public class ParentStructInfo { public string Number { get; set; } public SubjectAreaEnum SubjectArea { get; set; } public byte Index { get; set; } } public static class ExamPaperExtensions { public static AssignmentDto ConvertToExamDTO(this ExamPaper examPaper) { AssignmentDto dto = new AssignmentDto(); dto.Title = examPaper.AssignmentTitle; dto.Description = examPaper.Description; var SubjectArea = SubjectAreaEnum.Literature; Enum.TryParse(examPaper.SubjectArea, out SubjectArea); dto.SubjectArea = SubjectArea; AssignmentStructDto examStruct = new AssignmentStructDto(); foreach (var qg in examPaper.QuestionGroups) { examStruct.ChildrenGroups.Add(ParseMajorQuestionGroup(qg)); examStruct.ChildrenGroups.Last().Index = (byte)(examStruct.ChildrenGroups.Count()); } dto.ExamStruct = examStruct; return dto; } private static AssignmentStructDto ParseMajorQuestionGroup(MajorQuestionGroup sqg) { var examStruct = new AssignmentStructDto(); if (sqg.SubQuestionGroups != null) { examStruct.Title = sqg.Title; examStruct.Score = sqg.Score; examStruct.ChildrenGroups = new List(); sqg.SubQuestionGroups?.ForEach(ssqg => { if (string.IsNullOrEmpty(ssqg.Descript)) { examStruct.ChildrenGroups.Add(ParseMajorQuestionGroup(ssqg)); examStruct.ChildrenGroups.Last().Index = (byte)(examStruct.ChildrenGroups.Count()); } else { examStruct.AssignmentQuestions.Add(ParseGroupToAssignmentQuestion(ssqg, false)); examStruct.AssignmentQuestions.Last().Index = (byte)(examStruct.AssignmentQuestions.Count()); } }); } if (sqg.SubQuestions != null) { sqg.SubQuestions?.ForEach(sq => { if(sq.SubQuestions.Any()) { } examStruct.AssignmentQuestions.Add(ParseAssignmentQuestion(sq)); examStruct.AssignmentQuestions.Last().Index = (byte)(examStruct.AssignmentQuestions.Count()); }); } return examStruct; } public static List ParseOptionsFromText(this string optionsText) { return optionsText.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None) .Where(line => !string.IsNullOrWhiteSpace(line)).ToList(); } private static QuestionDto ParseGroupToQuestion(MajorQuestionGroup qg, bool subQ = true) { var dq = new QuestionDto(); dq.Title = qg.Title + Environment.NewLine + qg.Descript; if (subQ) dq.GroupState = QuestionGroupState.Subquestion; else dq.GroupState = QuestionGroupState.Group; qg.SubQuestions?.ForEach(ssq => { dq.ChildrenQuestion.Add(ParseQuestion(ssq)); }); qg.SubQuestionGroups?.ForEach(sqg => { dq.ChildrenQuestion.Add(ParseGroupToQuestion(sqg)); }); return dq; } private static AssignmentQuestionDto ParseGroupToAssignmentQuestion(MajorQuestionGroup qg, bool subQ = true) { var aq = new AssignmentQuestionDto(); aq.Score = qg.Score; qg.SubQuestions?.ForEach(ssq => { aq.Question.ChildrenQuestion.Add(ParseQuestion(ssq)); aq.Question.ChildrenQuestion.Last().Index = (byte)aq.Question.ChildrenQuestion.Count; }); qg.SubQuestionGroups?.ForEach(sqg => { aq.Question.ChildrenQuestion.Add(ParseGroupToQuestion(sqg)); aq.Question.ChildrenQuestion.Last().Index = (byte)aq.Question.ChildrenQuestion.Count; }); return aq; } private static AssignmentQuestionDto ParseAssignmentQuestion(PaperQuestion sq) { var aq = new AssignmentQuestionDto(); aq.Score = sq.Score; aq.Question = ParseQuestion(sq); sq.SubQuestions?.ForEach(ssq => { aq.Question.ChildrenQuestion.Add(ParseQuestion(ssq)); aq.Question.ChildrenQuestion.Last().Index = (byte)aq.Question.ChildrenQuestion.Count; }); return aq; } private static QuestionDto ParseQuestion(PaperQuestion sq) { var dq = new QuestionDto(); dq.Title = sq.Stem; dq.Options = string.Join(Environment.NewLine, sq.Options.Select(opt => $"{opt.Label} {opt.Text}")); dq.Score = sq.Score; sq.SubQuestions?.ForEach(ssq => { dq.ChildrenQuestion.Add(ParseQuestion(ssq)); dq.ChildrenQuestion.Last().Index = (byte)dq.ChildrenQuestion.Count; }); return dq; } private static void ParseMajorQuestionGroup(MajorQuestionGroup qg, QuestionGroupDto qgd, bool isParentGroupValidChain) { qgd.Title = qg.Title; qgd.Score = (int)qg.Score; qgd.Descript = qg.Descript; qgd.ValidQuestionGroup = !string.IsNullOrEmpty(qg.Descript) && !isParentGroupValidChain; bool nextIsParentGroupValidChain = qgd.ValidQuestionGroup || isParentGroupValidChain; if (qg.SubQuestionGroups != null) { qg.SubQuestionGroups.ForEach(sqg => { var sqgd = new QuestionGroupDto(); sqgd.Index = (byte)qg.SubQuestionGroups.IndexOf(sqg); ParseMajorQuestionGroup(sqg, sqgd, nextIsParentGroupValidChain); qgd.SubQuestionGroups.Add(sqgd); }); } if (qg.SubQuestions != null) { qg.SubQuestions.ForEach(sq => { if (sq.SubQuestions != null && sq.SubQuestions.Any()) { var subQgd = new QuestionGroupDto { Title = sq.Stem, Index = (byte)qg.SubQuestions.IndexOf(sq), Score = (int)sq.Score, Descript = "" // 默认为空 }; subQgd.ValidQuestionGroup = !string.IsNullOrEmpty(subQgd.Descript) && !nextIsParentGroupValidChain; ParseQuestionWithSubQuestions(sq, subQgd, subQgd.ValidQuestionGroup || nextIsParentGroupValidChain); qgd.SubQuestionGroups.Add(subQgd); } else // 如果 MajorQuestionGroup 下的 Question 没有子问题,则转为 SubQuestionDto { var subQd = new SubQuestionDto(); // 只有当所有父组(包括当前组)都不是有效组时,这个题目才有效 ParseSingleQuestion(sq, subQd, !nextIsParentGroupValidChain); subQd.Index = (byte)qg.SubQuestions.IndexOf(sq); qgd.SubQuestions.Add(subQd); } }); } } // 解析包含子问题的 Question,将其转换为 QuestionGroupDto // isParentGroupValidChain 参数表示从顶层到当前组的任一父组是否已经是“有效组” private static void ParseQuestionWithSubQuestions(PaperQuestion question, QuestionGroupDto qgd, bool isParentGroupValidChain) { qgd.Title = question.Stem; qgd.Score = (int)question.Score; qgd.Descript = ""; // 默认为空 // 判断当前组是否有效:如果有描述,并且其父级链中没有任何一个组是有效组,则当前组有效 qgd.ValidQuestionGroup = !string.IsNullOrEmpty(qgd.Descript) && !isParentGroupValidChain; // 更新传递给子项的 isParentGroupValidChain 状态 bool nextIsParentGroupValidChain = qgd.ValidQuestionGroup || isParentGroupValidChain; if (question.SubQuestions != null) { question.SubQuestions.ForEach(subQ => { // 如果子问题本身还有子问题(多层嵌套),则继续创建 QuestionGroupDto if (subQ.SubQuestions != null && subQ.SubQuestions.Any()) { var nestedQgd = new QuestionGroupDto { Title = subQ.Stem, Score = (int)subQ.Score, Descript = "" // 默认为空 }; // 判断当前组是否有效:如果有描述,并且其父级链中没有任何一个组是有效组,则当前组有效 nestedQgd.ValidQuestionGroup = !string.IsNullOrEmpty(nestedQgd.Descript) && !nextIsParentGroupValidChain; ParseQuestionWithSubQuestions(subQ, nestedQgd, nestedQgd.ValidQuestionGroup || nextIsParentGroupValidChain); qgd.SubQuestionGroups.Add(nestedQgd); } else // 如果子问题没有子问题,则直接创建 SubQuestionDto { var subQd = new SubQuestionDto(); // 只有当所有父组(包括当前组)都不是有效组时,这个题目才有效 ParseSingleQuestion(subQ, subQd, !nextIsParentGroupValidChain); qgd.SubQuestions.Add(subQd); } }); } } // 解析单个 Question (没有子问题) 为 SubQuestionDto private static void ParseSingleQuestion(PaperQuestion question, SubQuestionDto subQd, bool validQ) { subQd.Stem = question.Stem; subQd.Score = (int)question.Score; subQd.ValidQuestion = validQ; // 根据传入的 validQ 确定是否是“有效题目” subQd.SampleAnswer = question.SampleAnswer; subQd.QuestionType = question.QuestionType; // 注意:DifficultyLevel 在本地 Question 中没有,如果服务器需要,可能需要补充默认值或从其他地方获取 // subQd.DifficultyLevel = ...; if (question.Options != null) { question.Options.ForEach(o => { subQd.Options.Add(new OptionDto { Value = o.Label + o.Text }); }); } } public static void SeqIndex(this ExamDto dto) { dto.ExamStruct.SeqQGroupIndex(); } public static void SeqQGroupIndex(this QuestionGroupDto dto) { dto.SubQuestions?.ForEach(sq => { sq.Index = (byte)dto.SubQuestions.IndexOf(sq); }); dto.SubQuestionGroups?.ForEach(sqg => { sqg.Index = (byte)dto.SubQuestionGroups.IndexOf(sqg); sqg.SeqQGroupIndex(); }); } public static string SerializeExamDto(this ExamDto dto) { // 配置序列化选项(可选) var options = new JsonSerializerOptions { WriteIndented = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }; return JsonSerializer.Serialize(dto, options); } public static ExamDto DeserializeExamDto(string jsonString) { var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; return JsonSerializer.Deserialize(jsonString, options); } } }