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

又快开学了。。写一个课程表当练手。先用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<String> 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("<html><body><h1>河马课程表</h1></body></html>");
            ScheduleUtilities.setWeekLabels(labels, 1);
            labels[8].setText("<html><body><h2>第1-2节<br /></h2>上午</body></html>");
            labels[16].setText("<html><body><h2>第3-4节<br /></h2>上午</body></html>");
            labels[24].setText("<html><body><h2>第5-6节<br /></h2>下午</body></html>");
            labels[32].setText("<html><body><h2>第7-8节<br /></h2>下午</body></html>");
            labels[40].setText("<html><body><h2>第9-10节<br /></h2>晚上</body></html>");
        }
        
        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<ClassInfo> 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("<html><body><h2>星期一<br /></h2>" + date + "</body></html>");
        labels[2].setText("<html><body><h2>星期二<br /></h2>" + date.plusDays(1) + "</body></html>");
        labels[3].setText("<html><body><h2>星期三<br /></h2>" + date.plusDays(2) + "</body></html>");
        labels[4].setText("<html><body><h2>星期四<br /></h2>" + date.plusDays(3) + "</body></html>");
        labels[5].setText("<html><body><h2>星期五<br /></h2>" + date.plusDays(4) + "</body></html>");
        labels[6].setText("<html><body><h2>星期六<br /></h2>" + date.plusDays(5) + "</body></html>");
        labels[7].setText("<html><body><h2>星期日<br /></h2>" + date.plusDays(6) + "</body></html>");
    }
    
    /**
     * 根据{@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<ClassInfo> 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<ClassInfo> getWeekInfo(int week)
    {
        ArrayList<ClassInfo> needed = new ArrayList<>();
        for (ClassInfo info : infos)
            if (info.getWeeks().contains(week))
                needed.add(info);
        return needed;
    }
    
    private static ArrayList<Integer> getOddWeeks(ClassInfo info)
    {
        ArrayList<Integer> weeks = new ArrayList<>();
        for (Integer each : info.getWeeks())
            if ((each & 1) == 1)
                weeks.add(each);
        return weeks;
    }
    
    private static ArrayList<Integer> getEvenWeeks(ClassInfo info)
    {
        ArrayList<Integer> 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<String> 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<Integer> 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<Integer> 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<ClassInfo> 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<Integer> 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<Integer> getWeeks() { return weeks; }
    public void setWeeks(ArrayList<Integer> 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("<html><body><center>%s<br />教师:%s<br />@ %s<br />(%s)<br />%.1f学分"
                + (overlapped ? "<br /><b><font color=\"red\">课程重叠!</font></b>" : "")
                + "</center></body></html>",
                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());
    }
}

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

  • 5
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值