001package edu.pdx.cs410J.grader.canvas; 002 003import com.google.common.annotations.VisibleForTesting; 004import com.opencsv.CSVReader; 005import com.opencsv.exceptions.CsvValidationException; 006 007import java.io.IOException; 008import java.io.Reader; 009import java.util.ArrayList; 010import java.util.List; 011import java.util.SortedMap; 012import java.util.TreeMap; 013import java.util.regex.Matcher; 014import java.util.regex.Pattern; 015 016public class CanvasGradesCSVParser implements CanvasGradesCSVColumnNames { 017 private static final Pattern assignmentNamePattern = Pattern.compile("(.+) \\((\\d+)\\)"); 018 private static final String[] ignoredColumnNames = new String[] { 019 "ID", 020 "SIS User ID", 021 SECTION_COLUMN, 022 "Quizzes and Surveys Current Points", 023 "Quizzes and Surveys Final Points", 024 "Quizzes and Surveys Current Score", 025 "Quizzes and Surveys Unposted Current Score", 026 "Quizzes and Surveys Final Score", 027 "Quizzes and Surveys Unposted Final Score", 028 "Getting Ready Current Points", 029 "Getting Ready Final Points", 030 "Getting Ready Current Score", 031 "Getting Ready Unposted Current Score", 032 "Getting Ready Final Score", 033 "Getting Ready Unposted Final Score", 034 "Assignments Current Points", 035 "Assignments Final Points", 036 "Assignments Current Score", 037 "Assignments Unposted Current Score", 038 "Assignments Final Score", 039 "Assignments Unposted Final Score", 040 "Imported Assignments Current Points", 041 "Imported Assignments Final Points", 042 "Imported Assignments Current Score", 043 "Imported Assignments Unposted Current Score", 044 "Imported Assignments Final Score", 045 "Imported Assignments Unposted Final Score", 046 "Current Points", 047 "Final Points", 048 "Current Score", 049 "Unposted Current Score", 050 "Final Score", 051 "Unposted Final Score" 052 }; 053 054 private int studentNameColumn; 055 private int studentIdColumn; 056 private int canvasIdColumn; 057 private int sectionIdColumn; 058 private final SortedMap<Integer, GradesFromCanvas.CanvasAssignment> columnToAssignment = new TreeMap<>(); 059 private final GradesFromCanvas grades; 060 061 public CanvasGradesCSVParser(Reader reader) throws IOException { 062 CSVReader csv = new CSVReader(reader); 063 try { 064 extractColumnNamesFromFirstLineOfCsv(csv.readNext()); 065 extractPossiblePointsFromSecondLineOfCsv(csv.readNext()); 066 067 grades = new GradesFromCanvas(); 068 069 String[] studentLine; 070 while ((studentLine = csv.readNext()) != null) { 071 addStudentAndGradesFromLineOfCsv(studentLine); 072 } 073 074 } catch (CsvValidationException ex) { 075 throw new IOException("While parsing CSV", ex); 076 } 077 078 } 079 080 private void addStudentAndGradesFromLineOfCsv(String[] studentLine) { 081 GradesFromCanvas.CanvasStudent student = createStudentFrom(studentLine); 082 083 if (!student.getFirstName().equals("Test")) { 084 this.grades.addStudent(student); 085 } 086 087 addGradesFromLineOfCsv(student, studentLine); 088 } 089 090 private void addGradesFromLineOfCsv(GradesFromCanvas.CanvasStudent student, String[] studentLine) { 091 this.columnToAssignment.forEach((column, assignment) -> { 092 String score = studentLine[column]; 093 if (!isEmptyString(score)) { 094 student.setScore(assignment, parseScore(score)); 095 } 096 }); 097 098 } 099 100 private boolean isEmptyString(String score) { 101 return "".equals(score); 102 } 103 104 private double parseScore(String score) { 105 return Double.parseDouble(score); 106 } 107 108 private GradesFromCanvas.CanvasStudent createStudentFrom(String[] studentLine) { 109 String studentName = studentLine[studentNameColumn]; 110 Pattern studentNamePattern = Pattern.compile("(.*), (.*)"); 111 Matcher matcher = studentNamePattern.matcher(studentName); 112 if (matcher.matches()) { 113 GradesFromCanvas.CanvasStudentBuilder builder = GradesFromCanvas.newStudent(); 114 builder.setFirstName(matcher.group(2)); 115 builder.setLastName(matcher.group(1)); 116 builder.setLoginId(studentLine[studentIdColumn]); 117 builder.setCanvasId(studentLine[canvasIdColumn]); 118 builder.setSection(studentLine[sectionIdColumn]); 119 120 return builder.create(); 121 122 } else { 123 throw new IllegalStateException("Can't parse student name \"" + studentName + "\""); 124 } 125 } 126 127 private void extractPossiblePointsFromSecondLineOfCsv(String[] secondLine) { 128 this.columnToAssignment.forEach((column, assignment) -> { 129 String possiblePointsText = secondLine[column]; 130 try { 131 assignment.setPossiblePoints(Double.parseDouble(possiblePointsText)); 132 133 } catch (NumberFormatException ex) { 134 throw new IllegalStateException("Can't parse points \"" + possiblePointsText + "\" for " + assignment.getName()); 135 } 136 }); 137 138 } 139 140 private void extractColumnNamesFromFirstLineOfCsv(String[] firstLine) { 141 for (int i = 0; i < firstLine.length; i++) { 142 String cell = firstLine[i]; 143 switch (cell) { 144 case STUDENT_COLUMN: 145 this.studentNameColumn = i; 146 break; 147 case "SIS Login ID": 148 this.studentIdColumn = i; 149 break; 150 case ID_COLUMN: 151 this.canvasIdColumn = i; 152 break; 153 case SECTION_COLUMN: 154 this.sectionIdColumn = i; 155 break; 156 default: 157 if (!isColumnIgnored(cell)) { 158 addAssignment(cell, i); 159 } 160 } 161 } 162 163 } 164 165 @VisibleForTesting 166 static GradesFromCanvas.CanvasAssignment createAssignment(String assignmentText) { 167 Matcher matcher = assignmentNamePattern.matcher(assignmentText); 168 if (matcher.matches()) { 169 String name = matcher.group(1); 170 String idText = matcher.group(2); 171 return new GradesFromCanvas.CanvasAssignment(name, Integer.parseInt(idText)); 172 173 } else { 174 throw new IllegalStateException("Can't create Assignment from \"" + assignmentText + "\""); 175 } 176 } 177 178 private void addAssignment(String assignmentText, int column) { 179 this.columnToAssignment.put(column, createAssignment(assignmentText)); 180 181 } 182 183 private boolean isColumnIgnored(String columnName) { 184 for (String ignored : ignoredColumnNames) { 185 if (ignored.equals(columnName)) { 186 return true; 187 } 188 } 189 return false; 190 } 191 192 public List<GradesFromCanvas.CanvasAssignment> getAssignments() { 193 return new ArrayList<>(this.columnToAssignment.values()); 194 } 195 196 public GradesFromCanvas getGrades() { 197 return this.grades; 198 } 199 200}