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}