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.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 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("letter-grade")) { 211 String letterGrade = extractTextFrom(child); 212 student.setLetterGrade(LetterGrade.fromString(letterGrade)); 213 214 } else if (child.getTagName().equals("grades")) { 215 NodeList kids = child.getChildNodes(); 216 for (int j = 0; j < kids.getLength(); j++) { 217 Node kidNode = kids.item(j); 218 if (!(kidNode instanceof Element)) { 219 continue; 220 } 221 222 Element kid = (Element) kidNode; 223 if (kid.getTagName().equals("grade")) { 224 Grade grade = extractGradeFrom(kid); 225 student.setGrade(grade.getAssignmentName(), grade); 226 } 227 } 228 229 } else if (child.getTagName().equals("late")) { 230 NodeList kids = child.getChildNodes(); 231 for (int j = 0; j < kids.getLength(); j++) { 232 Node kidNode = kids.item(j); 233 if (!(kidNode instanceof Element)) { 234 continue; 235 } 236 237 Element kid = (Element) kidNode; 238 if (kid.getTagName().equals("name")) { 239 student.addLate(extractTextFrom(kid)); 240 } 241 } 242 243 } else if (child.getTagName().equals("resubmitted")) { 244 NodeList kids = child.getChildNodes(); 245 for (int j = 0; j < kids.getLength(); j++) { 246 Node kidNode = kids.item(j); 247 if (!(kidNode instanceof Element)) { 248 continue; 249 } 250 251 Element kid = (Element) kidNode; 252 if (kid.getTagName().equals("name")) { 253 student.addResubmitted(extractTextFrom(kid)); 254 } 255 } 256 257 } else if (child.getTagName().equals("notes")) { 258 for (String note : extractNotesFrom(child)) { 259 student.addNote(note); 260 } 261 } 262 } 263 264 if (student != null) { 265 if (root.hasAttribute("enrolled-section")) { 266 String section = root.getAttribute("enrolled-section"); 267 student.setEnrolledSection(Student.Section.fromString(section)); 268 } 269 270 // Students are initially clean 271 student.makeClean(); 272 } 273 274 return student; 275 } 276 277 private Element getRootElement(Document doc) throws ParserException { 278 Element root = null; 279 if (doc != null) { 280 root = doc.getDocumentElement(); 281 } 282 if (doc == null || root == null) { 283 throw new ParserException("Document parsing failed"); 284 } 285 286 if (!root.getTagName().equals("student")) { 287 String s = "XML data does not contain a student"; 288 throw new ParserException(s); 289 } 290 return root; 291 } 292 293 private Document parseXml() throws ParserException { 294 Document doc = null; 295 try { 296 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 297 factory.setValidating(true); 298 299 DocumentBuilder builder = factory.newDocumentBuilder(); 300 builder.setErrorHandler(this); 301 builder.setEntityResolver(this); 302 303 doc = builder.parse(new InputSource(this.reader)); 304 305 } catch (ParserConfigurationException | IOException | SAXException ex) { 306 throw new ParserException("While parsing XML source: " + ex); 307 } 308 return doc; 309 } 310 311 312}