简体   繁体   中英

C# Reading XML elements into 2 separate lists

I am creating a quiz in C# as console app.

I have a single XML file that contains a) questions b) answers and c) incorrect answers.

I can read questions from my XML File.

However I cannot work out the logic I need to associate the incorrect and correct answers for each randomly generated read question.

Here is a copy of my XML file.

<?xml version="1.0" encoding="utf-8"?>
<Question xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <theQuestion>How many players in a football team?</theQuestion>
  <answerA>12</answerA>
  <answerB>10</answerB>
  <answerC>20</answerC>
  <answerD>11</answerD>
  <correctAnswer>11</correctAnswer>
  <theQuestion>How many minutes in a football game?</theQuestion>
  <answerA>90</answerA>
  <answerB>45</answerB>
  <answerC>60</answerC>
  <answerD>77</answerD>
  <correctAnswer>90</correctAnswer> 
</Question>

Here is part of my code:

  ProcessData data = new ProcessData();

  //load questions from XML file and store in list
  var questions =  data.LoadQuizQuestions();

  //create a question object
  Question q = new Question();

  //get a question randomly generated from questions list 
  int index = new Random().Next(questions.Count);

  //display the randomly generated question
  Console.WriteLine(questions[index]);

  Console.ReadLine();

Here is my LoadQuizQuestions()

    public List<string> LoadQuizQuestions()
    {
      //create empty list to store quiz questions read in from file
      List<string> questions = new List<string>();

      //load questions from file into list
      questions =
        XDocument.Load(@"C:\Development\Learning\Files\qsFile.xml").Descendants("theQuestion").Select(o => o.Value).ToList();

      //return list of questions
      return questions;
   }

I would like that when each random question is displayed the associated answers to that question are also displayed and the "correct answer" read into a variable that I can check the user input against.

Please help me understand I know I am close to nailing this :-)

Thank you

  1. Read the xml into a List<Question> collection
  2. Pick a random item
    1. Show the question and the options
    2. Ask for the user input
    3. Compare the user input with the correct answer
  3. Profit

EDIT : Your XML input treats your data as sequential , not hierarchical; this will lead to potential problems when you tried to read the questions.

You should consider a structure like this:

<Questions>
    <Question>
        <Title>How many players in a football team?</Title>
        <Options>
            <Option>12</Option>
            <Option>10</Option>
            <Option>20</Option>
            <Option IsCorrect='true'>11</Option>
        </Options>
    </Question>
    <Question>
        <Title>How many minutes in a football game?</Title>
        <Options>
            <Option IsCorrect='true'>90</Option>
            <Option>45</Option>
            <Option>60</Option>
            <Option>77</Option>
        </Options>
    </Question>
</Questions>

This will make easier to read the XML manually, or deserialising it directly into a List<Question> collection.

I made the decision to keep in the option if it's a correct answer, as this can be flexible enough to multiple right answers.

class Question
{
    public string Title         { get; private set; }
    public List<Option> Options { get; private set; }

    public Question()
    {
    }

    public Question(XmlElement question) : this()
    {
        this.Title   = question["Title"].InnerText;
        this.Options = question.SelectNodes("Options/Option")
            .OfType<XmlElement>()
            .Select(option => new Option(option))
            .ToList();
    }
}

Not a big deal here: we just read an XmlElement and delegates to the Option class the item deserialization.

class Option
{
    public string Title         { get; private set; }
    public bool   IsCorrect     { get; private set; }

    public Option()
    {
    }

    public Option(XmlElement option) : this()
    {
        this.Title = option.InnerText;
        this.IsCorrect = option.GetAttribute("IsCorrect") == "true";
    }
}

Same deal.

With this structure, you can do something like this:

var xml = new XmlDocument();
    xml.LoadXml(@"...");

var random = new Random();
var questions = xml.SelectNodes("//Question")
    .OfType<XmlElement>()
    .Select (question => new Question(question))
    .OrderBy(question => random.Next())
    .ToList();

foreach (var question in questions)
{
    Console.ForegroundColor = ConsoleColor.White;
    Console.WriteLine(question.Title);
    foreach (var option in question.Options)
    {
        Console.ForegroundColor = ConsoleColor.Gray;
        Console.WriteLine("\t{0}", option.Title);
    }
    Console.Write("Choose the right option: ");
    var answer = Console.ReadLine();

    if (question.Options.Any(option =>
        option.IsCorrect && answer.Equals(option.Title, 
            StringComparison.InvariantCultureIgnoreCase)))
    {
        Console.ForegroundColor = ConsoleColor.Green;
        Console.WriteLine("YOU HAVE CHOSEN... WISELY.");
    }
    else
    {
        Console.ForegroundColor = ConsoleColor.Red;
        Console.WriteLine("You have chosen poorly!");
    }
}

If you use a question object that contains a list of answers, like this:

public class Question
{
    public int ID { get; set; }
    public string QuestionText { get; set; }

    public List<Answer> Answers { get; set; } 

    public string AnswerText { get; set; }
}

public class Answer
{
    public string ID { get; set; }
    public string AnswerText { get; set; }
}

Then you can read the questions and answers into discrete objects, something like the code below (disclaimer: did not test this so it may need to be tweaked to work)

   public List<Question> GetQuestions(string xmlFile)
    {
        var questions = new List<Question>();

        var xDoc = XDocument.Load(xmlFile);

        var questionNodes = xDoc.Descendants("theQuestion");

        foreach (var questionNode in questionNodes)
        {

            var question = new Question();
            question.QuestionText = questionNode.Value;

            // do something like this for all the answers
            var answer = new Answer();
            answer.ID = "A";
            var answerA = questionNode.Descendants("answerA").FirstOrDefault();
            if (answerA != null)
                answer.AnswerText = answerA.Value;

            question.Answers = new List<Answer>();
            question.Answers.Add(answer);

            question.AnswerText =
                questionNode.Descendants("correctAnswer").FirstOrDefault().Value;
        }

        return questions;
    } 
}

Now that you have the questions and answers in a single object you can display the question, the answers, and then based on user input do a string comparison to check the user's answer.

You can check my logic to get value form XMLNode as you want.

How to get node values in XML file using c#

If you can change your xml structure I'd do this:

<?xml version="1.0" encoding="utf-8"?>
<Questions>
  <Question text="How many players in a football team?">
    <answerA>12</answerA>
    <answerB>10</answerB>
    <answerC>20</answerC>
    <answerD>11</answerD>
    <correctAnswer>11</correctAnswer>
  </Question>
  <Question text="How many minutes in a football game?">
    <answerA>90</answerA>
    <answerB>45</answerB>
    <answerC>60</answerC>
    <answerD>77</answerD>
    <correctAnswer>90</correctAnswer>
  </Question>
</Questions>

Then deserialize using these classes:

public class Questions
{
    [XmlElement("Question")]
    public List<Question> QuestionList { get; set; } = new List<Question>();
}

public class Question
{
    [XmlAttribute("text")]
    public string Text { get; set; }

    public string answerA { get; set; }
    public string answerB { get; set; }
    public string answerC { get; set; }
    public string answerD { get; set; }
    public string correctAnswer { get; set; }
}

And this code:

string path = "yourxmlfile.xml";

XmlSerializer serializer = new XmlSerializer(typeof(Questions));

StreamReader reader = new StreamReader(path);
var qs = (Questions)serializer.Deserialize(reader);
reader.Close();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM