Skip to content
Snippets Groups Projects
Commit 43c766f8 authored by robinwilliam.hundt's avatar robinwilliam.hundt
Browse files

Added option to parse cloze questions

parent 86102c36
No related branches found
No related tags found
No related merge requests found
......@@ -10,7 +10,7 @@ use semver::Version;
use serde_derive::Deserialize;
use structopt::StructOpt;
use rusty_hektor::exam::{Exam, ExamSerializable, merge_exams};
use rusty_hektor::exam::{merge_exams, Exam, ExamSerializable};
use rusty_hektor::module::Module;
use rusty_hektor::parser::ipynb_parser::notebook::Notebook;
use rusty_hektor::parser::xml_parser::XMLParser;
......@@ -47,6 +47,10 @@ struct Opt {
#[structopt(long = "skip-text")]
skip_text: bool,
/// Parse cloze type questions
#[structopt(long = "parse-cloze")]
parse_cloze: bool,
/// Disable latex rendering
#[structopt(long = "no-latex")]
no_latex: bool,
......@@ -102,6 +106,7 @@ fn main() -> Result<(), Box<dyn Error>> {
let opt = Opt::from_args();
let parse_text = opt.skip_text.not();
let parse_cloze = opt.parse_cloze;
let render_latex = opt.no_latex.not();
let parsed_data: Result<Vec<Exam>, Box<dyn Error>> = opt
......@@ -118,7 +123,12 @@ fn main() -> Result<(), Box<dyn Error>> {
);
std::process::exit(1)
} else if extension == "zip" {
Ok(XMLParser::parse(path, parse_text, render_latex)?)
Ok(XMLParser::parse(
path,
parse_text,
parse_cloze,
render_latex,
)?)
} else {
Err(ParserError::new(format!(
"Unsupported filetype: {:?}",
......@@ -129,7 +139,7 @@ fn main() -> Result<(), Box<dyn Error>> {
.collect();
let parsed_data = parsed_data?;
let parsed_data= merge_exams(&parsed_data)?;
let parsed_data = merge_exams(&parsed_data)?;
let mut serializable = parsed_data.into_serializable()?;
......
......@@ -5,6 +5,7 @@ pub enum ValidationError {
EmptyField(EmptyFieldError),
InvalidStringLength(InvalidStringLengthError),
InvalidMatNo(InvalidMatrNumber),
AttributeNotPresent(AttributeNotPresent),
}
impl fmt::Display for ValidationError {
......@@ -13,6 +14,7 @@ impl fmt::Display for ValidationError {
ValidationError::EmptyField(ref e) => e.fmt(f),
ValidationError::InvalidMatNo(ref e) => e.fmt(f),
ValidationError::InvalidStringLength(ref e) => e.fmt(f),
ValidationError::AttributeNotPresent(ref e) => e.fmt(f),
}
}
}
......@@ -107,3 +109,22 @@ impl fmt::Display for MergeError {
}
impl Error for MergeError {}
#[derive(Debug)]
pub struct AttributeNotPresent {
attribute: &'static str,
}
impl AttributeNotPresent {
pub fn new(attribute: &'static str) -> Self {
Self { attribute }
}
}
impl fmt::Display for AttributeNotPresent {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "attribute not found: \"{}\" ", self.attribute)
}
}
impl Error for AttributeNotPresent {}
use std::collections::{BTreeMap};
use std::collections::BTreeSet;
use std::collections::{BTreeMap, HashSet};
use std::error::Error;
use crate::errors::{EmptyFieldError, MergeError};
use itertools::Itertools;
use crate::errors::{EmptyFieldError};
use crate::meta_information::MetaInformation;
use crate::module::Module;
use crate::student::Student;
use crate::student::StudentSerializable;
use crate::submission_type::SubmissionType;
use crate::yes_or_no;
use itertools::Itertools;
#[derive(Debug, Default)]
pub struct Exam {
......@@ -74,7 +74,7 @@ pub fn merge_exams(exams: &[Exam]) -> Result<Exam, Box<dyn Error>> {
Ok(Exam {
module: None,
submission_types,
students
students,
})
}
......
......@@ -10,6 +10,7 @@ pub trait Parser {
fn parse<'a>(
path: &Path,
parse_text_questions: bool,
parse_cloze_question: bool,
render_latex: bool,
) -> result::Result<Exam, Box<dyn Error>>;
}
......
......@@ -16,7 +16,8 @@ use zip::ZipArchive;
use lazy_static::lazy_static;
use crate::errors::{
EmptyFieldError, InvalidMatrNumber, InvalidStringLengthError, ValidationError,
AttributeNotPresent, EmptyFieldError, InvalidMatrNumber, InvalidStringLengthError,
ValidationError,
};
use crate::exam::Exam;
use crate::parser::{Parser, ParserError};
......@@ -89,18 +90,26 @@ impl Parser for XMLParser {
fn parse(
path: &Path,
parse_text_questions: bool,
parse_cloze_question: bool,
render_latex: bool,
) -> result::Result<Exam, Box<dyn Error>> {
let file = File::open(path)?;
let mut archive = ZipArchive::new(file)?;
let xml_files = XMLFiles::new(&mut archive)?;
let allowed_question_types: &[&str] = if parse_text_questions {
&["assSourceCode", "TEXT QUESTION"]
} else {
&["assSourceCode"]
let allowed_question_types: Vec<&str> = {
let mut allowed = vec!["assSourceCode"];
if parse_text_questions {
allowed.push("TEXT QUESTION");
}
if parse_cloze_question {
allowed.push("CLOZE QUESTION");
}
allowed
};
let sub_types = extract_submission_types(&xml_files, allowed_question_types, render_latex)?;
let sub_types =
extract_submission_types(&xml_files, &allowed_question_types, render_latex)?;
let students = extract_students(&xml_files, &sub_types)?;
Ok(Exam {
......@@ -195,9 +204,7 @@ fn extract_students(
let element = item
.element()
.expect(&format!("No element for {:?} present", item));
let mut matr_nr = element
.attribute_value("matr_nr")
.expect("No matr_nr attr found");
let matr_nr = element.attribute_value("matr_nr");
let active_id = element
.attribute_value("active_id")
.expect("No active_id attr found");
......@@ -205,22 +212,25 @@ fn extract_students(
.attribute_value("fullname")
.expect("No fullname attr found");
match validate_mat_no(&matr_nr) {
Err(ValidationError::EmptyField(_)) => {
let matr_nr = match validate_mat_no(matr_nr) {
Err(ValidationError::EmptyField(_)) | Err(ValidationError::AttributeNotPresent(_)) => {
warn!(
"matr_nr for student {} is empty, falling back to active_id",
fullname
);
matr_nr = active_id;
active_id
}
Err(e) => warn!(
"matr_no of {} has wrong format: {}\n{}",
fullname,
matr_nr,
e.description()
),
_ => {}
}
Err(e) => {
warn!(
"matr_no of {} has wrong format: {:?}\n Falling back to active_id\n{}",
fullname,
matr_nr,
e.description()
);
active_id
}
Ok(()) => matr_nr.unwrap(),
};
let submissions =
extract_submissions_for_student(results_doc, active_id, submission_types)?;
......@@ -238,7 +248,16 @@ fn extract_students(
Ok(results)
}
fn validate_mat_no(mat_no: &str) -> Result<(), ValidationError> {
fn validate_mat_no(mat_no: Option<&str>) -> Result<(), ValidationError> {
let mat_no = match mat_no {
Some(val) => val,
None => {
return Err(ValidationError::AttributeNotPresent(
AttributeNotPresent::new("matr_no"),
))
}
};
if mat_no.len() == 0 {
Err(ValidationError::EmptyField(EmptyFieldError::new(
"Unknown student",
......@@ -314,18 +333,28 @@ fn extract_submission_for_student(
.into_iter()
.last();
let code = match oldest {
Some(node) => {
let elem = node
.element()
.expect(&format!("No element for {:?} present", node));
String::from_utf8(decode(
elem.attribute_value("value1")
.expect("No value1 attr found, unable to parse code"),
)?)?
let result_el = oldest.map(|node| {
node.element()
.expect(&format!("No element for {:?} present", node))
});
let encoded_attr = result_el.map(|el| {
let mut val = el
.attribute_value("value1")
.expect("No value1 attr found, unable to parse code");
if val == "0" {
val = el
.attribute_value("value2")
.expect("value1 is 0 but value2 attr not found, unable to parse code");
}
None => "".to_owned(),
val
});
let code = match encoded_attr {
Some(val) => String::from_utf8(decode(val)?)?,
None => "".to_string(),
};
let submission_type = sub_type.name.clone();
Ok(Submission {
......
......@@ -67,7 +67,13 @@ impl SubmissionType {
}
}
if self.programming_language.is_none() {
self.programming_language = Some(input("Programming language"));
self.programming_language = Some(input(
format!(
"Programming language (available: {})",
ProgrammingLang::list_available()
)
.as_str(),
));
}
Ok(())
......@@ -116,11 +122,18 @@ pub enum ProgrammingLang {
haskell,
python,
plaintext,
markdown,
}
impl ProgrammingLang {
fn list_available() -> &'static str {
return "c, java, mipsasm, haskell, plaintext, markdown, python";
}
}
#[derive(Debug, Display)]
#[display(
fmt = "Unparseable programming language: {}. Allowed: c, java, mipsasm, haskell, python, plaintext",
fmt = "Unparseable programming language: {}. Allowed: c, java, mipsasm, haskell, python, plaintext, markdown",
input
)]
pub struct ParseProgrammingLangError {
......@@ -139,6 +152,7 @@ impl FromStr for ProgrammingLang {
"mipsasm" => ProgrammingLang::mipsasm,
"haskell" => ProgrammingLang::haskell,
"plaintext" => ProgrammingLang::plaintext,
"markdown" => ProgrammingLang::markdown,
"python" => ProgrammingLang::python,
_ => Err(ParseProgrammingLangError {
input: s.to_owned(),
......
......@@ -7,7 +7,7 @@ use std::collections::{BTreeSet, HashSet};
#[test]
fn can_parse_zipped_xml_data() -> Result<(), Box<dyn Error>> {
let parsed = XMLParser::parse(Path::new("tests/test.zip"), false, true)?;
let parsed = XMLParser::parse(Path::new("tests/test.zip"), false, false,true)?;
let serializable = parsed.into_serializable()?;
assert_eq!(1, serializable.submission_types.len());
assert_eq!(1, serializable.students.len());
......@@ -16,7 +16,7 @@ fn can_parse_zipped_xml_data() -> Result<(), Box<dyn Error>> {
#[test]
fn parsed_xml_contains_correct_submission_types() -> Result<(), Box<dyn Error>> {
let parsed = XMLParser::parse(Path::new("tests/test.zip"), false, true)?;
let parsed = XMLParser::parse(Path::new("tests/test.zip"), false, false, true)?;
let serializable = parsed.into_serializable()?;
let submission_type_names: HashSet<String> = serializable
......@@ -37,7 +37,7 @@ fn parsed_xml_contains_correct_submission_types() -> Result<(), Box<dyn Error>>
#[test]
fn parsed_xls_contains_correct_students() -> Result<(), Box<dyn Error>> {
let parsed = XMLParser::parse(Path::new("tests/test.zip"), false, true)?;
let parsed = XMLParser::parse(Path::new("tests/test.zip"), false, false,true)?;
let serializable = parsed.into_serializable()?;
let students: BTreeSet<StudentSerializable> = serializable
......@@ -68,7 +68,7 @@ fn parsed_xls_contains_correct_students() -> Result<(), Box<dyn Error>> {
fn correct_mapping_is_generated() -> Result<(), Box<dyn Error>> {
use rusty_hektor::anonymizer;
let parsed = XMLParser::parse(Path::new("tests/test.zip"), false, true)?;
let parsed = XMLParser::parse(Path::new("tests/test.zip"), false, false,true)?;
let mut serializable = parsed.into_serializable()?;
let map = anonymizer::replace_students(&mut serializable.students);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment