001package edu.pdx.cs.joy.grader.gradebook; 002 003import edu.pdx.cs.joy.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.File; 015import java.io.FileReader; 016import java.io.IOException; 017import java.io.Reader; 018import java.time.LocalDateTime; 019import java.util.ArrayList; 020import java.util.List; 021 022/** 023 * This class parses XML data that describes a <code>Student</code>. 024 */ 025public class XmlStudentParser extends XmlHelper { 026 027 /** The Reader from which the XML Data is read */ 028 private final Reader reader; 029 030 /////////////////////// Constructors //////////////////////// 031 032 /** 033 * Creates a new <code>XmlStudentParser</code> that reads XML data 034 * from a given <code>Reader</code> 035 */ 036 public XmlStudentParser(Reader reader) { 037 this.reader = reader; 038 } 039 040 /** 041 * Creates a new <code>XmlStudentParser</code> that reads XML data 042 * from a given <code>File</code> 043 */ 044 public XmlStudentParser(File file) throws IOException { 045 this(new FileReader(file)); 046 } 047 048 ///////////////////// Instance Methods ////////////////////// 049 050 /** 051 * Extracts a <code>Grade</code> from a chuck of a DOM tree 052 */ 053 private Grade extractGradeFrom(Element root) throws ParserException { 054 String name = null; 055 String score = null; 056 List<String> notes = null; 057 List<Grade.SubmissionInfo> submissions = null; 058 059 NodeList kids = root.getChildNodes(); 060 for (int j = 0; j < kids.getLength(); j++) { 061 Node kidNode = kids.item(j); 062 if (!(kidNode instanceof Element)) { 063 continue; 064 } 065 066 Element kid = (Element) kidNode; 067 if (kid.getTagName().equals("name")) { 068 name = extractTextFrom(kid); 069 070 } else if (kid.getTagName().equals("score")) { 071 score = extractTextFrom(kid); 072 073 } else if (kid.getTagName().equals("notes")) { 074 notes = extractNotesFrom(kid); 075 076 } else if (kid.getTagName().equals("submissions")) { 077 submissions = extractSubmissionsFrom(kid); 078 } 079 } 080 081 if (name == null || score == null) { 082 throw new ParserException("Malformed grade"); 083 } 084 085 try { 086 double s = Double.parseDouble(score); 087 Grade grade = new Grade(name, s); 088 if (notes != null) { 089 notes.forEach(grade::addNote); 090 } 091 if (submissions != null) { 092 submissions.forEach(grade::noteSubmission); 093 } 094 095 return grade; 096 097 } catch (NumberFormatException ex) { 098 throw new ParserException("Malformatted number: " + score); 099 } 100 } 101 102 private static List<Grade.SubmissionInfo> extractSubmissionsFrom(Element parent) { 103 List<Grade.SubmissionInfo> list = new ArrayList<>(); 104 105 NodeList children = parent.getChildNodes(); 106 for (int i = 0; i < children.getLength(); i++) { 107 Node node = children.item(i); 108 if (!(node instanceof Element)) { 109 continue; 110 } 111 112 Element child = (Element) node; 113 if (child.getTagName().equals("submission-info")) { 114 list.add(extractSubmissionFrom(child)); 115 116 } else if (child.getTagName().equals("submission")) { 117 Grade.SubmissionInfo info = new Grade.SubmissionInfo(); 118 setSubmissionTimeFromTextInNode(info, child); 119 list.add(info); 120 } 121 } 122 123 return list; 124 } 125 126 private static Grade.SubmissionInfo extractSubmissionFrom(Element parent) { 127 Grade.SubmissionInfo info = new Grade.SubmissionInfo(); 128 if (parent.hasAttribute("late")) { 129 if ("true".equals(parent.getAttribute("late"))) { 130 info.setIsLate(true); 131 } 132 } 133 134 NodeList children = parent.getChildNodes(); 135 for (int i = 0; i < children.getLength(); i++) { 136 Node node = children.item(i); 137 if (!(node instanceof Element)) { 138 continue; 139 } 140 141 Element child = (Element) node; 142 if (child.getTagName().equals("date")) { 143 setSubmissionTimeFromTextInNode(info, child); 144 145 } else if (child.getTagName().equals("estimated-hours")) { 146 setSubmissionEstimatedHoursFromTextInNode(info, child); 147 } 148 } 149 150 return info; 151 } 152 153 private static void setSubmissionEstimatedHoursFromTextInNode(Grade.SubmissionInfo info, Element node) { 154 String text = extractTextFrom(node); 155 Double estimatedHours = Double.parseDouble(text); 156 info.setEstimatedHours(estimatedHours); 157 } 158 159 private static void setSubmissionTimeFromTextInNode(Grade.SubmissionInfo info, Element node) { 160 String text = extractTextFrom(node); 161 LocalDateTime submitTime = LocalDateTime.parse(text, DATE_TIME_FORMAT); 162 info.setSubmissionTime(submitTime); 163 } 164 165 /** 166 * Creates a new <code>Student</code> from the contents of a file. 167 */ 168 public Student parseStudent() throws ParserException { 169 Document doc = parseXml(); 170 Element root = getRootElement(doc); 171 172 Student student = null; 173 174 NodeList children = root.getChildNodes(); 175 for (int i = 0; i < children.getLength(); i++) { 176 Node node = children.item(i); 177 if (!(node instanceof Element)) { 178 continue; 179 } 180 181 Element child = (Element) node; 182 if (child.getTagName().equals("id")) { 183 String idFromFile = extractTextFrom(child); 184 student = new Student(idFromFile); 185 186 } else if (child.getTagName().equals("firstName")) { 187 String firstName = extractTextFrom(child); 188 student.setFirstName(firstName); 189 190 } else if (child.getTagName().equals("lastName")) { 191 String lastName = extractTextFrom(child); 192 student.setLastName(lastName); 193 194 } else if (child.getTagName().equals("nickName")) { 195 String nickName = extractTextFrom(child); 196 student.setNickName(nickName); 197 198 } else if (child.getTagName().equals("email")) { 199 String email = extractTextFrom(child); 200 student.setEmail(email); 201 202 } else if (child.getTagName().equals("major")) { 203 String major = extractTextFrom(child); 204 student.setMajor(major); 205 206 } else if (child.getTagName().equals("canvas-id")) { 207 String canvasId = extractTextFrom(child); 208 student.setCanvasId(canvasId); 209 210 } else if (child.getTagName().equals("github-user-name")) { 211 String gitHubUserName = extractTextFrom(child); 212 student.setGitHubUserName(gitHubUserName); 213 214 } else if (child.getTagName().equals("letter-grade")) { 215 String letterGrade = extractTextFrom(child); 216 student.setLetterGrade(LetterGrade.fromString(letterGrade)); 217 218 } else if (child.getTagName().equals("grades")) { 219 NodeList kids = child.getChildNodes(); 220 for (int j = 0; j < kids.getLength(); j++) { 221 Node kidNode = kids.item(j); 222 if (!(kidNode instanceof Element)) { 223 continue; 224 } 225 226 Element kid = (Element) kidNode; 227 if (kid.getTagName().equals("grade")) { 228 Grade grade = extractGradeFrom(kid); 229 student.setGrade(grade.getAssignmentName(), grade); 230 } 231 } 232 233 } else if (child.getTagName().equals("late")) { 234 NodeList kids = child.getChildNodes(); 235 for (int j = 0; j < kids.getLength(); j++) { 236 Node kidNode = kids.item(j); 237 if (!(kidNode instanceof Element)) { 238 continue; 239 } 240 241 Element kid = (Element) kidNode; 242 if (kid.getTagName().equals("name")) { 243 student.addLate(extractTextFrom(kid)); 244 } 245 } 246 247 } else if (child.getTagName().equals("resubmitted")) { 248 NodeList kids = child.getChildNodes(); 249 for (int j = 0; j < kids.getLength(); j++) { 250 Node kidNode = kids.item(j); 251 if (!(kidNode instanceof Element)) { 252 continue; 253 } 254 255 Element kid = (Element) kidNode; 256 if (kid.getTagName().equals("name")) { 257 student.addResubmitted(extractTextFrom(kid)); 258 } 259 } 260 261 } else if (child.getTagName().equals("notes")) { 262 for (String note : extractNotesFrom(child)) { 263 student.addNote(note); 264 } 265 } 266 } 267 268 if (student != null) { 269 if (root.hasAttribute("enrolled-section")) { 270 String section = root.getAttribute("enrolled-section"); 271 student.setEnrolledSection(Student.Section.fromString(section)); 272 } 273 274 // Students are initially clean 275 student.makeClean(); 276 } 277 278 return student; 279 } 280 281 private Element getRootElement(Document doc) throws ParserException { 282 Element root = null; 283 if (doc != null) { 284 root = doc.getDocumentElement(); 285 } 286 if (doc == null || root == null) { 287 throw new ParserException("Document parsing failed"); 288 } 289 290 if (!root.getTagName().equals("student")) { 291 String s = "XML data does not contain a student"; 292 throw new ParserException(s); 293 } 294 return root; 295 } 296 297 private Document parseXml() throws ParserException { 298 Document doc = null; 299 try { 300 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 301 factory.setValidating(true); 302 303 DocumentBuilder builder = factory.newDocumentBuilder(); 304 builder.setErrorHandler(this); 305 builder.setEntityResolver(this); 306 307 doc = builder.parse(new InputSource(this.reader)); 308 309 } catch (ParserConfigurationException | IOException | SAXException ex) { 310 throw new ParserException("While parsing XML source: " + ex); 311 } 312 return doc; 313 } 314 315 316}