Java: 将课程表解析成每周课表

又快开学了。。写一个课程表当练手。先用HttpWatcher把课程爬下来存进txt里(代码省略),然后解析成每周课表,就像超级课程表一样。爬虫的结果如下:

Java: 将课程表解析成每周课表_第1张图片

 

最终效果如下:

Java: 将课程表解析成每周课表_第2张图片

Java: 将课程表解析成每周课表_第3张图片

 

代码如下:

// 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()); } }

在进阶的路上,欢迎各位大神指正。

你可能感兴趣的:(Java: 将课程表解析成每周课表)