001package edu.pdx.cs410J.grader.gradebook; 002 003import edu.pdx.cs410J.ParserException; 004import org.w3c.dom.Document; 005import org.w3c.dom.Element; 006import org.w3c.dom.Node; 007import org.w3c.dom.NodeList; 008import org.xml.sax.InputSource; 009import org.xml.sax.SAXException; 010 011import javax.xml.parsers.DocumentBuilder; 012import javax.xml.parsers.DocumentBuilderFactory; 013import javax.xml.parsers.ParserConfigurationException; 014import java.io.*; 015import java.time.LocalDateTime; 016import java.time.format.DateTimeParseException; 017 018import static edu.pdx.cs410J.grader.gradebook.Assignment.ProjectType.*; 019import static edu.pdx.cs410J.grader.gradebook.GradeBook.LetterGradeRanges.LetterGradeRange; 020 021/** 022 * This class creates a <code>GradeBook</code> from the contents of an 023 * XML file. 024 */ 025public class XmlGradeBookParser extends XmlHelper { 026 private GradeBook book; // The grade book we're creating 027 private InputStream in; // Input source of grade book 028 private File studentDir; // Where the student XML file live 029 030 /** 031 * Creates an <code>XmlGradeBookParser</code> that creates a 032 * <code>GradeBook</code> from a file of a given name. 033 */ 034 public XmlGradeBookParser(String fileName) throws IOException { 035 this(new File(fileName)); 036 } 037 038 /** 039 * Creates an <code>XmlGradeBookParser</code> that creates a 040 * <code>GradeBook</code> from the contents of a <code>File</code>. 041 */ 042 public XmlGradeBookParser(File file) throws IOException { 043 this(new FileInputStream(file)); 044 this.setStudentDir(file.getCanonicalFile().getParentFile()); 045 } 046 047 /** 048 * Creates an <code>XmlGradeBookParser</code> that creates a 049 * <code>GradeBook</code> from the contents of an 050 * <code>InputStream</code>. 051 */ 052 XmlGradeBookParser(InputStream in) { 053 this.in = in; 054 } 055 056 /** 057 * Sets the directory in which the XML files for students are 058 * generated. 059 */ 060 public void setStudentDir(File dir) { 061 if (!dir.exists()) { 062 throw new IllegalArgumentException(dir + " does not exist"); 063 } 064 065 if (!dir.isDirectory()) { 066 throw new IllegalArgumentException(dir + " is not a directory"); 067 } 068 069 this.studentDir = dir; 070 } 071 072 /** 073 * Extracts an <code>Assignment</code> from an <code>Element</code> 074 */ 075 private static Assignment extractAssignmentFrom(Element element) 076 throws ParserException { 077 Assignment assign = null; 078 079 String name = null; 080 String description = null; 081 082 NodeList children = element.getChildNodes(); 083 for (int i = 0; i < children.getLength(); i++) { 084 Node node = children.item(i); 085 if (!(node instanceof Element)) { 086 continue; 087 } 088 089 Element child = (Element) node; 090 if (child.getTagName().equals("name")) { 091 name = extractTextFrom(child); 092 093 } else if (child.getTagName().equals("description")) { 094 description = extractTextFrom(child); 095 096 } else if (child.getTagName().equals("points")) { 097 String points = extractTextFrom(child); 098 try { 099 if (name == null) { 100 throw new ParserException("No name for assignment with " + 101 "points " + points); 102 } 103 assign = new Assignment(name, Double.parseDouble(points)); 104 if (description != null) { 105 assign.setDescription(description); 106 } 107 108 } catch (NumberFormatException ex) { 109 throw new ParserException("Invalid points value: " + 110 points); 111 } 112 113 } else if (child.getTagName().equals("canvas-id")) { 114 String canvasId = extractTextFrom(child); 115 try { 116 assign.setCanvasId(Integer.parseInt(canvasId)); 117 118 } catch(NumberFormatException ex) { 119 throw new ParserException("Invalidate Canvas Id: " + canvasId, ex); 120 } 121 122 } else if (child.getTagName().equals("due-date")) { 123 String dueDate = extractTextFrom(child); 124 try { 125 assign.setDueDate(LocalDateTime.parse(dueDate, DATE_TIME_FORMAT)); 126 127 } catch(DateTimeParseException ex) { 128 throw new ParserException("Invalidate LocalDateTime: " + dueDate, ex); 129 } 130 131 } else if (child.getTagName().equals("notes")) { 132 for (String note : extractNotesFrom(child)) { 133 assign.addNote(note); 134 } 135 } 136 } 137 138 if (assign == null ) { 139 throw new ParserException("No assignment found!"); 140 } 141 142 setAssignmentTypeFromXml(element, assign); 143 144 return assign; 145 } 146 147 private static void setAssignmentTypeFromXml(Element assignmentElement, Assignment assignment) throws ParserException { 148 String type = assignmentElement.getAttribute("type"); 149 switch (type) { 150 case "PROJECT": 151 assignment.setType(Assignment.AssignmentType.PROJECT); 152 setProjectTypeFromXml(assignmentElement, assignment); 153 break; 154 case "QUIZ": 155 assignment.setType(Assignment.AssignmentType.QUIZ); 156 break; 157 case "OTHER": 158 assignment.setType(Assignment.AssignmentType.OTHER); 159 break; 160 case "OPTIONAL": 161 assignment.setType(Assignment.AssignmentType.OPTIONAL); 162 break; 163 case "POA": 164 assignment.setType(Assignment.AssignmentType.POA); 165 break; 166 default: 167 throw new ParserException("Unknown assignment type: " + type); 168 } 169 } 170 171 private static void setProjectTypeFromXml(Element assignmentElement, Assignment assignment) throws ParserException { 172 String projectType = assignmentElement.getAttribute("project-type"); 173 if (projectType != null && projectType.length() > 0) { 174 switch (projectType) { 175 case "APP_CLASSES": 176 assignment.setProjectType(APP_CLASSES); 177 break; 178 179 case "TEXT_FILE": 180 assignment.setProjectType(TEXT_FILE); 181 break; 182 183 case "PRETTY_PRINT": 184 assignment.setProjectType(PRETTY_PRINT); 185 break; 186 187 case "KOANS": 188 assignment.setProjectType(KOANS); 189 break; 190 191 case "XML": 192 assignment.setProjectType(XML); 193 break; 194 195 case "REST": 196 assignment.setProjectType(REST); 197 break; 198 199 case "ANDROID": 200 assignment.setProjectType(ANDROID); 201 break; 202 203 default: 204 throw new ParserException("Unknown project type: " + projectType); 205 } 206 } 207 } 208 209 /** 210 * Parses the source and from it creates a <code>GradeBook</code>. 211 */ 212 public GradeBook parse() throws ParserException { 213 Document doc = parseDocumentFromInputStream(); 214 215 216 Element root = null; 217 if (doc != null) { 218 root = doc.getDocumentElement(); 219 } 220 if (doc == null || root == null) { 221 throw new ParserException("Document parsing failed"); 222 } 223 224 NodeList children = root.getChildNodes(); 225 for (int i = 0; i < children.getLength(); i++) { 226 Node node = children.item(i); 227 if (!(node instanceof Element)) { 228 continue; 229 } 230 231 Element child = (Element) node; 232 if (child.getTagName().equals("name")) { 233 this.book = new GradeBook(extractTextFrom(child)); 234 this.book.setDirty(false); 235 236 } else if (this.book == null) { 237 throw new ParserException("name element is not first"); 238 239 } else if (child.getTagName().equals("assignments")) { 240 extractAssignmentsFrom(child); 241 242 } else if (child.getTagName().equals("letter-grade-ranges")) { 243 extractLetterGradeRangesFrom(child); 244 245 } else if (child.getTagName().equals("section-name")) { 246 extractSectionName(child); 247 248 } else if (child.getTagName().equals("students")) { 249 extractStudentsFrom(child); 250 251 } else if (child.getTagName().equals("lateDays")) { 252 // Fill in later, maybe. 253 } 254 } 255 256 if (this.book != null) { 257 // The book is initially clean 258 this.book.makeClean(); 259 } 260 261 return this.book; 262 } 263 264 private void extractSectionName(Element element) { 265 Student.Section section = extractSection(element); 266 String sectionName = extractTextFrom(element); 267 this.book.setSectionName(section, sectionName); 268 } 269 270 private void extractLetterGradeRangesFrom(Element parent) { 271 Student.Section section = extractSection(parent); 272 273 NodeList ranges = parent.getChildNodes(); 274 for (int j = 0; j < ranges.getLength(); j++) { 275 Node range = ranges.item(j); 276 277 if (!(range instanceof Element)) { 278 continue; 279 } 280 281 extractLetterGradeRangeFrom((Element) range, section); 282 } 283 } 284 285 private Student.Section extractSection(Element parent) { 286 Student.Section section; 287 String attributeName = "for-section"; 288 if (parent.hasAttribute(attributeName)) { 289 String value = parent.getAttribute(attributeName); 290 section = Student.Section.fromString(value); 291 292 } else { 293 section = Student.Section.UNDERGRADUATE; 294 } 295 return section; 296 } 297 298 private void extractLetterGradeRangeFrom(Element element, Student.Section section) { 299 String letterGradeString = element.getAttribute("letter-grade"); 300 LetterGrade letterGrade = LetterGrade.fromString(letterGradeString); 301 LetterGradeRange range = this.book.getLetterGradeRanges(section).getRange(letterGrade); 302 range.setRange(toInt(element.getAttribute("minimum-score")), toInt(element.getAttribute("maximum-score"))); 303 304 } 305 306 private int toInt(String value) { 307 return Integer.parseInt(value); 308 } 309 310 private void extractStudentsFrom(Element child) throws ParserException { 311 NodeList students = child.getChildNodes(); 312 for (int j = 0; j < students.getLength(); j++) { 313 Node student = students.item(j); 314 315 if (!(student instanceof Element)) { 316 continue; 317 } 318 319 Element idElement = (Element) student; 320 if (idElement.getTagName().equals("id")) { 321 String id = extractTextFrom(idElement); 322 // Locate the XML file for the Student 323 File file = 324 new File(this.studentDir, id + ".xml"); 325 if (!file.exists()) { 326 throw new IllegalArgumentException("No XML file for " + 327 id); 328 } 329 330 try { 331 XmlStudentParser sp = new XmlStudentParser(file); 332 Student stu = sp.parseStudent(); 333 this.book.addStudent(stu); 334 335 } catch (Exception ex) { 336 String s = "While parsing " + file + ": " + ex; 337 throw new ParserException(s); 338 } 339 } 340 } 341 } 342 343 private void extractAssignmentsFrom(Element child) throws ParserException { 344 NodeList assignments = child.getChildNodes(); 345 for (int j = 0; j < assignments.getLength(); j++) { 346 Node assignment = assignments.item(j); 347 if (assignment instanceof Element) { 348 Assignment assign = 349 extractAssignmentFrom((Element) assignment); 350 this.book.addAssignment(assign); 351 } 352 } 353 } 354 355 private Document parseDocumentFromInputStream() throws ParserException { 356 // Parse the source 357 Document doc; 358 359 // Create a DOM tree from the XML source 360 try { 361 DocumentBuilderFactory factory = 362 DocumentBuilderFactory.newInstance(); 363 factory.setValidating(true); 364 365 DocumentBuilder builder = 366 factory.newDocumentBuilder(); 367 builder.setErrorHandler(this); 368 builder.setEntityResolver(this); 369 370 doc = builder.parse(new InputSource(this.in)); 371 372 } catch (ParserConfigurationException | SAXException | IOException ex) { 373 throw new ParserException("While parsing XML source: " + ex); 374 375 } 376 return doc; 377 } 378 379}