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}