package uk.co.bytemark.vm.enigma.inquisition.questions.xml;
import java.text.ParseException;
import org.jdom.DocType;
import org.jdom.Document;
import org.jdom.Element;
import uk.co.bytemark.vm.enigma.inquisition.misc.ReadableElement;
import uk.co.bytemark.vm.enigma.inquisition.misc.Utils;
import uk.co.bytemark.vm.enigma.inquisition.questions.DragAndDropQuestion;
import uk.co.bytemark.vm.enigma.inquisition.questions.MultipleChoiceQuestion;
import uk.co.bytemark.vm.enigma.inquisition.questions.Option;
import uk.co.bytemark.vm.enigma.inquisition.questions.ParsingProblemRecorder;
import uk.co.bytemark.vm.enigma.inquisition.questions.Question;
import uk.co.bytemark.vm.enigma.inquisition.questions.QuestionSet;
import uk.co.bytemark.vm.enigma.inquisition.questions.MultipleChoiceQuestion.Builder;
public class XmlQuestionSetParser {
private static final int QUESTION_SET_EXPORT_VERSION = 4;
public static class QuestionSetXML {
private QuestionSetXML() { // Cannot instantiate class
}
public static final String TAG_QUESTION_SET = "QuestionSet";
public static final String ATTRIBUTE_VERSION = "version";
public static final String TAG_QUESTIONS = "Questions";
public static final String TAG_RECOMMENDED_TIME_PER_QUESTION = "RecommendedTimePerQuestion";
public static final String TAG_DESCRIPTION = "Description";
public static final String TAG_NAME = "Name";
public static final String TAG_CATEGORY = "Category";
}
public static class QuestionXML {
private QuestionXML() {
// Cannot instantiate class
}
public static final String TAG_QUESTION_TEXT = "QuestionText";
public static final String TAG_EXPLANATION_TEXT = "ExplanationText";
}
public static class MultipleChoiceQuestionXML {
private MultipleChoiceQuestionXML() { // Cannot instantiate class
}
public static final String TAG_MULTIPLE_CHOICE_QUESTION = "MultipleChoiceQuestion";
public static final String ATTRIBUTE_SHUFFLABLE = "shufflable";
public static final String ATTRIBUTE_SINGLE_OPTION_MODE = "singleOptionMode";
public static final String TAG_OPTIONS = "Options";
public static final String TAG_OPTION = "Option";
}
/**
* Constructs a new <tt>MultipleChoiceQuestion</tt> from a JDOM XML Element
*
* @param element
* the JDOM XML element to parse.
* @param parsingProblemRecorder
* @throws ParseException
* if any vital information can't be found.
*/
public MultipleChoiceQuestion parseMultipleChoiceQuestion(Element element,
ParsingProblemRecorder parsingProblemRecorder) throws ParseException {
Utils.checkArgumentNotNull(element, "element");
Builder builder = new Builder();
if (!element.getName().equals(MultipleChoiceQuestionXML.TAG_MULTIPLE_CHOICE_QUESTION)) {
String message = "Outer tag must be " + MultipleChoiceQuestionXML.TAG_MULTIPLE_CHOICE_QUESTION;
parsingProblemRecorder.recordFatal(message);
throw new ParseException(message, 0);
}
processXmlQuestionAttributes(element, builder, parsingProblemRecorder);
for (Object object : element.getChildren())
processXmlSubTag((Element) object, builder, parsingProblemRecorder);
checkThatAllNecessaryInformationIsPresent(builder, parsingProblemRecorder);
return builder.build();
}
private void processXmlQuestionAttributes(Element element, Builder builder,
ParsingProblemRecorder parsingProblemRecorder) throws ParseException {
String attributeValue = element.getAttributeValue(MultipleChoiceQuestionXML.ATTRIBUTE_SINGLE_OPTION_MODE);
if (attributeValue == null) {
String message = "No " + MultipleChoiceQuestionXML.ATTRIBUTE_SINGLE_OPTION_MODE + " attribute on "
+ MultipleChoiceQuestionXML.TAG_MULTIPLE_CHOICE_QUESTION + ", defaulting to "
+ builder.isSingleOptionMode();
parsingProblemRecorder.recordWarning(message);
} else {
builder.singleOptionMode(Utils.parseBoolean(attributeValue));
}
attributeValue = element.getAttributeValue(MultipleChoiceQuestionXML.ATTRIBUTE_SHUFFLABLE);
if (attributeValue == null) {
String message = "No " + MultipleChoiceQuestionXML.ATTRIBUTE_SHUFFLABLE + " attribute on "
+ MultipleChoiceQuestionXML.TAG_MULTIPLE_CHOICE_QUESTION + ", defaulting to "
+ builder.isShufflable();
parsingProblemRecorder.recordWarning(message);
} else {
builder.shufflable(Utils.parseBoolean(attributeValue));
}
}
private void processXmlSubTag(Element subElement, Builder builder, ParsingProblemRecorder parsingProblemRecorder)
throws ParseException {
String name = subElement.getName();
if (name.equalsIgnoreCase(QuestionXML.TAG_QUESTION_TEXT)) {
builder.questionText(subElement.getText());
} else if (name.equalsIgnoreCase(QuestionXML.TAG_EXPLANATION_TEXT)) {
builder.explanationText(subElement.getText());
} else if (name.equalsIgnoreCase(MultipleChoiceQuestionXML.TAG_OPTIONS)) {
processXmlOptions(subElement, builder, parsingProblemRecorder);
} else {
parsingProblemRecorder.recordWarning("Unknown tag while parsing elements under <"
+ MultipleChoiceQuestionXML.TAG_MULTIPLE_CHOICE_QUESTION + ">, skipping: " + name);
}
}
private void processXmlOptions(Element subElement, Builder builder, ParsingProblemRecorder parsingProblemRecorder)
throws ParseException {
int count = 1;
for (Object child : subElement.getChildren()) {
Element optionElement = (Element) child;
if (optionElement.getName().equalsIgnoreCase(MultipleChoiceQuestionXML.TAG_OPTION)) {
builder.option(new Option(optionElement, count));
count++;
} else {
parsingProblemRecorder.recordWarning("Unknown tag while parsing elements under <"
+ MultipleChoiceQuestionXML.TAG_OPTIONS + ">, skipping: " + optionElement.getName());
}
}
}
private void checkThatAllNecessaryInformationIsPresent(Builder builder,
ParsingProblemRecorder parsingProblemRecorder) throws ParseException {
if (builder.getExplanationText() == null) { // We can live without an explanation, but flag it
parsingProblemRecorder.recordWarning(QuestionXML.TAG_EXPLANATION_TEXT + " for "
+ MultipleChoiceQuestionXML.TAG_MULTIPLE_CHOICE_QUESTION + " missing");
builder.explanationText("");
}
if (builder.getQuestionText() == null) {
String message = "No questionText found in " + MultipleChoiceQuestionXML.TAG_MULTIPLE_CHOICE_QUESTION + "";
parsingProblemRecorder.recordFatal(message);
throw new ParseException(message, 0);
}
if (builder.getOptions().size() == 0) {
String message = "No options found in " + MultipleChoiceQuestionXML.TAG_MULTIPLE_CHOICE_QUESTION + "";
parsingProblemRecorder.recordFatal(message);
throw new ParseException(message, 0);
}
int correctOptions = MultipleChoiceQuestion.countCorrectOptions(builder.getOptions());
if (correctOptions == 0) {
String message = "No correct options found in " + MultipleChoiceQuestionXML.TAG_MULTIPLE_CHOICE_QUESTION
+ "";
parsingProblemRecorder.recordFatal(message);
throw new ParseException(message, 0);
}
if (builder.isSingleOptionMode() && correctOptions > 1) {
parsingProblemRecorder.recordError("Inconsistent question: "
+ MultipleChoiceQuestionXML.ATTRIBUTE_SINGLE_OPTION_MODE + ", yet " + correctOptions
+ " correct options given." + " Deactivating "
+ MultipleChoiceQuestionXML.ATTRIBUTE_SINGLE_OPTION_MODE + " for this question.");
builder.singleOptionMode(false);
}
}
public Element asXML(MultipleChoiceQuestion question) {
ReadableElement element = new ReadableElement(MultipleChoiceQuestionXML.TAG_MULTIPLE_CHOICE_QUESTION);
// Add attributes
element.setAttribute(MultipleChoiceQuestionXML.ATTRIBUTE_SHUFFLABLE, Boolean.toString(question.isShufflable()));
element.setAttribute(MultipleChoiceQuestionXML.ATTRIBUTE_SINGLE_OPTION_MODE, Boolean.toString(question
.isSingleOptionMode()));
// Create sub-tags
ReadableElement questionTextElement = new ReadableElement(QuestionXML.TAG_QUESTION_TEXT);
questionTextElement.setText(question.getQuestionText());
ReadableElement explanationTextElement = new ReadableElement(QuestionXML.TAG_EXPLANATION_TEXT);
explanationTextElement.setText(question.getExplanationText());
ReadableElement optionsElement = new ReadableElement(MultipleChoiceQuestionXML.TAG_OPTIONS);
for (Option option : question.getOptions())
optionsElement.addContent(option.asXML());
// Add sub-tags
element.addContent(questionTextElement);
element.addContent(optionsElement);
element.addContent(explanationTextElement);
return element;
}
public Document asXmlDocument(QuestionSet questionSet) {
Document doc = new Document(new XmlQuestionSetParser().asXML(questionSet));
doc.setDocType(new DocType("QuestionSet", "inquisitionQuestions.dtd"));
return doc;
}
public Element asXML(QuestionSet questionSet) {
Element element = new Element(QuestionSetXML.TAG_QUESTION_SET);
element.setAttribute(QuestionSetXML.ATTRIBUTE_VERSION, Integer.toString(QUESTION_SET_EXPORT_VERSION));
ReadableElement nameElement = new ReadableElement(QuestionSetXML.TAG_NAME);
nameElement.setText(questionSet.getName());
element.addContent(nameElement);
ReadableElement descriptionElement = new ReadableElement(QuestionSetXML.TAG_DESCRIPTION);
descriptionElement.setText(questionSet.getDescription());
element.addContent(descriptionElement);
Element recommendedTimeElement = new Element(QuestionSetXML.TAG_RECOMMENDED_TIME_PER_QUESTION);
recommendedTimeElement.setText(Integer.toString(questionSet.getRecommendedTimePerQuestion()));
element.addContent(recommendedTimeElement);
Element categoryElement = new Element(QuestionSetXML.TAG_CATEGORY);
categoryElement.setText(questionSet.getCategorySequence());
element.addContent(categoryElement);
Element questionsElement = new Element(QuestionSetXML.TAG_QUESTIONS);
for (Question question : questionSet.getQuestions()) {
Element questionElement;
if (question instanceof MultipleChoiceQuestion)
questionElement = asXML((MultipleChoiceQuestion) question);
else if (question instanceof DragAndDropQuestion)
questionElement = ((DragAndDropQuestion) question).asXML();
else
throw new IllegalArgumentException("Unknown question type " + question.getClass().getName());
questionsElement.addContent(questionElement);
}
element.addContent(questionsElement);
return element;
}
}