又快开学了。。写一个课程表当练手。先用HttpWatcher把课程爬下来存进txt里(代码省略),然后解析成每周课表,就像超级课程表一样。爬虫的结果如下:
最终效果如下:
代码如下:
// Coding starts here
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.border.EmptyBorder;
import javax.swing.filechooser.FileNameExtensionFilter;
/**
* GUI主窗口。显示课程表主体
* 最后一次修改时间:Feb. 22nd, 2019
* @author Hippo
* @since Feb. 16th, 2019
*/
public class ScheduleTableGUI extends JFrame
{
private static final long serialVersionUID = -3144698016849055495L;
private JPanel contentPane;
private JLabel[] labels;
private JComboBox comboBox;
private static String title = "河马课程表 - null";
/**
* Launch the application.
*/
public static void main(String[] args)
{
EventQueue.invokeLater(() ->
{
try
{
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
}
catch (Exception whatever) // There's nothing (I could / to) do, sorry.
{
}
try
{
ScheduleUtilities.readCurricula(Paths.get("./src/justdowhateveryouwant/curricula.txt"));
ScheduleTableGUI frame = new ScheduleTableGUI();
frame.setVisible(true);
}
catch (Exception e)
{
e.printStackTrace();
String method = "set";
if (e instanceof IOException)
method = "read";
JOptionPane.showMessageDialog(null,
method + "Curricula() method failed. More information:\r\n" + e.getClass().getName() + ": " + e.getMessage(),
"河马课程表", JOptionPane.ERROR_MESSAGE);
}
});
}
/**
* Create the frame.
*/
public ScheduleTableGUI()
{
setTitle(title);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 1336, 819);
setResizable(false);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
contentPane.setLayout(new BorderLayout(0, 0));
setContentPane(contentPane);
JPanel northFlowPanel = new JPanel(new FlowLayout());
contentPane.add(northFlowPanel, BorderLayout.NORTH);
JPanel centerNullPanel = new JPanel(null);
centerNullPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
contentPane.add(centerNullPanel, BorderLayout.CENTER);
Color darkerGray = Color.GRAY.darker();
Color ligherBlack = darkerGray.darker().darker().darker();
labels = new JLabel[48];
for (int i = 0; i < 6; i++)
{
for (int j = 0; j < 8; j++)
{
int index = i * 8 + j;
labels[index] = new JLabel();
labels[index].setBounds(j * 165, i * 125, 165, 125);
labels[index].setOpaque(true);
labels[index].setHorizontalAlignment(JTextField.CENTER);
if ((i & 1) == 0)
labels[index].setBackground((j & 1) == 0 ? ligherBlack : darkerGray);
else
labels[index].setBackground((j & 1) == 0 ? darkerGray : ligherBlack);
labels[index].setForeground(Color.WHITE);
centerNullPanel.add(labels[index]);
}
}
{
// BLOCK ALERT: Should never be modified unless regulations changed
labels[0].setText("河马课程表
");
ScheduleUtilities.setWeekLabels(labels, 1);
labels[8].setText("第1-2节
上午");
labels[16].setText("第3-4节
上午");
labels[24].setText("第5-6节
下午");
labels[32].setText("第7-8节
下午");
labels[40].setText("第9-10节
晚上");
}
comboBox = new JComboBox<>();
comboBox.addActionListener(event ->
{
int week = comboBox.getSelectedIndex() + 1;
ScheduleUtilities.setCurricula(week, labels);
ScheduleUtilities.setWeekLabels(labels, week);
setTitle(title);
});
comboBox.setSize(330, 30);
for (int i = 1; i < 23; i++)
comboBox.addItem(String.format(" 第 %2d 周 ", i));
northFlowPanel.add(comboBox);
JButton sttstcsBtn = new JButton("数据统计");
sttstcsBtn.addActionListener(event -> {
String statistics = ScheduleUtilities.statistics();
String[] buttons = { " 确定 ", " 导出 ", " 取消 " };
if (JOptionPane.showOptionDialog(null, statistics, "河马课程表", 0, 1, null, buttons, buttons[0]) == 1)
{
JFileChooser chooser = new JFileChooser(".");
chooser.setAcceptAllFileFilterUsed(false);
chooser.setFileFilter(new FileNameExtensionFilter("文本文件(*.txt)", "txt"));
chooser.showOpenDialog(this);
String saveTo = chooser.getSelectedFile().getAbsolutePath();
if (!saveTo.endsWith(".txt"))
saveTo += ".txt";
try (PrintWriter pw = new PrintWriter(saveTo))
{
pw.println("[河马课程表]\r\n[Statistics][导出时间:" + LocalDate.now() + " " + LocalTime.now()
+ "]\r\n" + statistics);
JOptionPane.showMessageDialog(null, "导出完成", "河马课程表", JOptionPane.INFORMATION_MESSAGE);
}
catch (IOException e)
{
JOptionPane.showMessageDialog(null,
"文件读写时出错。 More information:\r\n" + e.getClass().getName() + ": " + e.getMessage(),
"河马课程表", JOptionPane.ERROR_MESSAGE);
}
}
});
northFlowPanel.add(sttstcsBtn);
}
public static void resetTitle(String t) { title = t; }
}
/**
* 课表实用类
* 包含所有课程的信息集合、解析文件、设置课程等方法
* @author Hippo
*/
class ScheduleUtilities
{
private static ArrayList infos;
/**
* 设置周一到周日的标签(带日期)
* @param labels
*/
public static void setWeekLabels(JLabel[] labels, int week)
{
LocalDate date = LocalDate.of(2018, 3, 5).plusDays((week - 1) * 7);
labels[1].setText("星期一
" + date + "");
labels[2].setText("星期二
" + date.plusDays(1) + "");
labels[3].setText("星期三
" + date.plusDays(2) + "");
labels[4].setText("星期四
" + date.plusDays(3) + "");
labels[5].setText("星期五
" + date.plusDays(4) + "");
labels[6].setText("星期六
" + date.plusDays(5) + "");
labels[7].setText("星期日
" + date.plusDays(6) + "");
}
/**
* 根据{@code infos}向表格添加课程
* @param week 当前周数
* @param labels JLabel数组传参
*/
public static void setCurricula(int week, JLabel[] labels)
{
for (int i = 9; i < 48; i++)
{
if (i == 16 || i == 24 || i == 32 || i == 40)
continue;
labels[i].setText("");
}
ArrayList needed = getWeekInfo(week);
int overlapped = 0;
for (ClassInfo each : needed)
{
int index = getIndex(each);
if (labels[index].getText().equals(""))
labels[index].setText(each.toScheduleString(false));
else
{
overlapped = 4;
labels[index].setText(each.toScheduleString(true));
}
}
if (overlapped == 4)
JOptionPane.showMessageDialog(null, "发现本周存在课程重叠(已标记),您无法修得所有课程!", "河马课程表", JOptionPane.ERROR_MESSAGE);
}
/**
* 获得当前周的所有课程
* @param week 当前周数
* @return
*/
private static ArrayList getWeekInfo(int week)
{
ArrayList needed = new ArrayList<>();
for (ClassInfo info : infos)
if (info.getWeeks().contains(week))
needed.add(info);
return needed;
}
private static ArrayList getOddWeeks(ClassInfo info)
{
ArrayList weeks = new ArrayList<>();
for (Integer each : info.getWeeks())
if ((each & 1) == 1)
weeks.add(each);
return weeks;
}
private static ArrayList getEvenWeeks(ClassInfo info)
{
ArrayList weeks = new ArrayList<>();
for (Integer each : info.getWeeks())
if ((each & 1) == 0)
weeks.add(each);
return weeks;
}
/**
* 根据课程获取JLabel数组的角标
* @param each
* @return
*/
private static int getIndex(ClassInfo info)
{
int weekday = info.getWeekday();
int time = info.getStartTime();
return (time + 1) / 2 * 8 + weekday;
}
/**
* 根据文件解析课程
* @param path 文件路径
* @throws IOException
*/
public static void readCurricula(Path path) throws IOException
{
infos = new ArrayList<>();
List lines = Files.readAllLines(path, Charset.forName("gbk"));
ScheduleTableGUI.resetTitle("河马课程表 - " + lines.get(0));
int flag = 0;
ClassInfo info = null;
for (int i = 1; i < lines.size(); i++)
{
String currentLine = lines.get(i);
if (currentLine == null || currentLine.equals("")) // 跳过空行
continue;
if (currentLine.contains("|")) // 则该行为“课程名 | 任课教师 | 授课周数 | 学分”,flag为0
{
info = new ClassInfo();
String[] strs = lines.get(i).split(" *\\| *");
info.setName(strs[0]);
info.setProfessor(strs[1]);
String[] weeksStr = strs[2].replaceAll("[^0-9\\-,]", "").split(",");
ArrayList weeks = new ArrayList<>();
for (String week : weeksStr)
{
if (week.contains("-"))
{
String[] dual = week.split("\\-");
int start = Integer.parseInt(dual[0]);
int end = Integer.parseInt(dual[1]);
for (int k = start; k <= end; k++)
weeks.add(k);
}
else
weeks.add(Integer.parseInt(week));
}
info.setWeeks(weeks);
info.setCredit(Double.parseDouble(strs[3]));
flag = 0;
}
else // 则该行为“星期Xa-b节,地点,校区[,单双周限制]”,flag为1。假设a和b只相差1
{
if (flag == 1)
info = ClassInfo.cloneHalf(infos.get(infos.size() - 1));
String[] strs = lines.get(i).split(",");
int weekday = 0;
switch (strs[0].charAt(2))
{
case '一':
default:
weekday = 1;
break;
case '二':
weekday = 2;
break;
case '三':
weekday = 3;
break;
case '四':
weekday = 4;
break;
case '五':
weekday = 5;
break;
case '六':
weekday = 6;
break;
case '日':
weekday = 7;
}
info.setWeekday(weekday);
info.setStartTime(Integer.parseInt("" + strs[0].charAt(3)));
info.setClassroom(strs[1]);
info.setCampus(strs[2]);
if (strs.length == 4)
{
if (strs[3].equals("单周"))
info.setOdd(true);
else if (strs[3].equals("双周"))
info.setEven(true);
}
flag = 1;
ArrayList weeks = null;
if (info.isOdd())
weeks = getOddWeeks(info);
else if (info.isEven())
weeks = getEvenWeeks(info);
else
weeks = info.getWeeks();
info.setWeeks(weeks);
infos.add(info);
}
}
// for (ClassInfo each : infos)
// System.out.println(each);
}
/**
* 统计本学期课程数据
*/
public static String statistics()
{
StringBuilder sb = new StringBuilder("以下数据按照学分倒序排列:\r\n");
ArrayList merged = new ArrayList<>();
double totalCredit = 0;
BIG_LOOP:
for (int d = 0; d < infos.size(); d++)
{
ClassInfo info = infos.get(d);
info.setTotal(info.getWeeks().size());
for (int i = 0; i < merged.size(); i++)
{
ClassInfo current = merged.get(i);
if (current.equals(info))
{
current.setTotal(current.getToTal() + info.getToTal());
continue BIG_LOOP;
}
}
merged.add(info);
totalCredit += info.getCredit();
}
merged.sort((a, b) ->
{
return Double.compare(b.getCredit(), a.getCredit());
});
int totalClasses = 0;
for (ClassInfo each : merged)
{
sb.append(each.toStatisticalString() + "\r\n");
totalClasses += each.getToTal();
}
sb.append("\r\n若成功活过本学期的" + totalClasses + "堂课,你将至少获得" + String.format("%.1f", totalCredit) + "学分!\r\n");
return sb.toString();
}
}
/**
* 包含每门课所有信息的课程类
* @author Hippo
*/
class ClassInfo
{
private String name; // 课程名称
private String professor; // 任课教师
private ArrayList weeks; // 上课的周数
private int weekday; // 星期几上课
private int startTime; // 上课时间(取第一个序号)
private String classroom; // 上课地点
private String campus; // 校区
private boolean odd = false; // 单周上课
private boolean even = false; // 双周上课
private double credit; // 学分
private int total; // 本学期该课程总课数
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getProfessor() { return professor; }
public void setProfessor(String professor) { this.professor = professor; }
public ArrayList getWeeks() { return weeks; }
public void setWeeks(ArrayList weeks) { this.weeks = weeks; }
public int getWeekday() { return weekday; }
public void setWeekday(int weekday) { this.weekday = weekday; }
public int getStartTime() { return startTime; }
public void setStartTime(int startTime) { this.startTime = startTime; }
public String getClassroom() { return classroom; }
public void setClassroom(String classroom) { this.classroom = classroom; }
public String getCampus() { return campus; }
public void setCampus(String campus) { this.campus = campus; }
public boolean isOdd() { return odd; }
public void setOdd(boolean odd) { this.odd = odd; }
public boolean isEven() { return even; }
public void setEven(boolean even) { this.even = even; }
public double getCredit() { return credit; }
public void setCredit(double credit) { this.credit = credit; }
public int getToTal() { return total; }
public void setTotal(int total) { this.total = total; }
/**
* 每门课信息不止一行时,每行都算作一门新课
* 用此方法返回初始化了一半参数的新ClassInfo对象
* @param info 最后一次创建的ClassInfo对象
* @return 新ClassInfo对象
*/
public static ClassInfo cloneHalf(ClassInfo info)
{
ClassInfo newInfo = new ClassInfo();
newInfo.setName(info.getName());
newInfo.setProfessor(info.getProfessor());
newInfo.setWeeks(info.getWeeks());
newInfo.setCredit(info.getCredit());
return newInfo;
}
@Override
public String toString()
{
return String.format(
"[课程名称=%s, 任课教师=%s, 周数=%s, 星期=%d, 时间=%d, 地点=%s, 校区=%s, 单周=%b, 双周=%b,学分=%.1f]",
name, professor, weeks, weekday, startTime, classroom, campus, odd, even, credit);
}
/**
* 课程表中每个格子的格式
* @param overlapped 是否存在课程重叠
* @return 格式化后的字符串
*/
public String toScheduleString(boolean overlapped)
{
return String.format("%s
教师:%s
@ %s
(%s)
%.1f学分"
+ (overlapped ? "
课程重叠!" : "")
+ " ",
name, professor, classroom, campus, credit);
}
public String toStatisticalString()
{
return String.format("[%.1f学分][%s] 共计%d节课,即%d个学时,%.1f个小时", credit, name, total, total * 2, total * 1.5);
}
/**
* 若两门课的名称一致,则认为两门课相同
* @return boolean
*/
@Override
public boolean equals(Object obj)
{
if (obj == null || !(obj instanceof ClassInfo))
return false;
if (obj == this)
return true;
return getName().equals(((ClassInfo) obj).getName());
}
}
在进阶的路上,欢迎各位大神指正。