解决Competitive Companion解析AtCoder题目时的TypeError问题

解决Competitive Companion解析AtCoder题目时的TypeError问题

【免费下载链接】competitive-companion Browser extension which parses competitive programming problems 【免费下载链接】competitive-companion 项目地址: https://gitcode.com/gh_mirrors/co/competitive-companion

问题背景与现象

在使用Competitive Companion浏览器扩展解析AtCoder平台题目时,部分用户遇到了TypeError错误。这个问题导致无法正确获取题目信息,影响了 competitive programming(程序设计竞赛)选手的日常训练效率。错误通常发生在以下场景:

  • 打开AtCoder竞赛题目页面时
  • 通过竞赛任务列表批量解析题目时
  • 切换不同语言版本的题目描述时

技术原理分析

Competitive Companion工作流程

Competitive Companion解析题目的核心流程如下:

mermaid

AtCoder解析器实现

AtCoder题目解析主要由AtCoderProblemParser类负责,其核心代码结构如下:

export class AtCoderProblemParser extends Parser {
  public getMatchPatterns(): string[] {
    return ['https://atcoder.jp/contests/*/tasks/*'];
  }

  public async parse(url: string, html: string): Promise<Sendable> {
    const elem = htmlToElement(html);
    const task = new TaskBuilder('AtCoder').setUrl(url);

    // 提取题目名称
    const name = [...elem.querySelector('h2, .h2').childNodes]
      .filter(node => node.nodeType === Node.TEXT_NODE)
      .map(node => node.textContent)
      .join('')
      .trim();

    // 设置其他题目属性...
    
    return task.build();
  }
}

错误原因定位

经过代码分析,TypeError错误主要有以下两个可能原因:

1. DOM元素选择器问题

AtCoder网站可能更新了页面结构,导致解析器中的选择器无法找到目标元素:

// 风险代码
const name = [...elem.querySelector('h2, .h2').childNodes]
// 如果elem.querySelector('h2, .h2')返回null,将导致后续操作抛出TypeError

当页面结构变化导致querySelector返回null时,尝试访问childNodes属性就会抛出TypeError: Cannot read property 'childNodes' of null

2. 匹配模式验证失败

扩展内部的URL匹配验证逻辑可能导致TypeError:

// 相关代码:match-pattern-to-reg-exp.ts
throw new TypeError(`"${pattern}" is not a valid MatchPattern`);
throw new TypeError(`"${pattern}" does not have a valid host`);

如果AtCoder的URL格式发生变化,可能导致匹配模式验证失败,从而抛出TypeError。

解决方案

方案一:增强DOM查询健壮性

修改AtCoderProblemParser.ts中的元素选择逻辑,增加空值检查:

// 原代码
const name = [...elem.querySelector('h2, .h2').childNodes]
  .filter(node => node.nodeType === Node.TEXT_NODE)
  .map(node => node.textContent)
  .join('')
  .trim();

// 修改后
const titleElement = elem.querySelector('h2, .h2');
if (!titleElement) {
  throw new Error('无法找到标题元素');
}

const name = [...titleElement.childNodes]
  .filter(node => node.nodeType === Node.TEXT_NODE)
  .map(node => node.textContent?.trim() || '')
  .join('')
  .trim();

方案二:优化URL匹配模式

检查并更新AtCoderProblemParser的URL匹配模式:

// 修改getMatchPatterns方法
public getMatchPatterns(): string[] {
  return [
    'https://atcoder.jp/contests/*/tasks/*',
    'https://atcoder.jp/contests/*/tasks/*?lang=*' // 新增对语言参数的支持
  ];
}

方案三:完善错误处理机制

在解析流程中增加try-catch块,提供更友好的错误提示:

public async parse(url: string, html: string): Promise<Sendable> {
  try {
    const elem = htmlToElement(html);
    const task = new TaskBuilder('AtCoder').setUrl(url);
    
    // 解析逻辑...
    
    return task.build();
  } catch (error) {
    console.error(`解析AtCoder题目时出错: ${error.message}`, error);
    // 提供错误恢复机制或备用解析方案
    throw new Error(`解析失败: ${error.message}`);
  }
}

实施步骤

1. 获取源码

git clone https://gitcode.com/gh_mirrors/co/competitive-companion
cd competitive-companion

2. 修改相关文件

src/parsers/problem/AtCoderProblemParser.ts
// 在parse方法中增加错误处理和空值检查
public async parse(url: string, html: string): Promise<Sendable> {
  try {
    const elem = htmlToElement(html);
    const task = new TaskBuilder('AtCoder').setUrl(url);

    // 提取题目名称(增强版)
    const titleElement = elem.querySelector('h2, .h2');
    if (!titleElement) {
      throw new Error('无法找到标题元素');
    }

    const name = [...titleElement.childNodes]
      .filter(node => node.nodeType === Node.TEXT_NODE)
      .map(node => node.textContent?.trim() || '')
      .join('')
      .trim();

    task.setName(name);
    
    // 提取比赛类别(增强版)
    const contestElement = elem.querySelector('.contest-name, .contest-title');
    if (contestElement) {
      task.setCategory(contestElement.textContent);
    } else {
      console.warn('无法找到比赛类别元素');
      // 尝试从URL中提取比赛信息作为备选方案
      const match = url.match(/contests\/([^\/]+)\/tasks/);
      if (match && match[1]) {
        task.setCategory(match[1]);
      }
    }

    // 处理交互式题目标记
    const interactiveSentences = ['This is an interactive task', 'This is a reactive problem'];
    task.setInteractive(interactiveSentences.some(x => html.includes(x)));

    // 提取时间和内存限制(增强版)
    const limitNodes = elem.querySelector('#task-statement')?.previousElementSibling;
    if (limitNodes) {
      const timeLimitMatch = limitNodes.textContent.match(/([0-9.]+) ?sec/);
      if (timeLimitMatch && timeLimitMatch[1]) {
        task.setTimeLimit(parseFloat(timeLimitMatch[1]) * 1000);
      }

      const memoryLimitMatch = limitNodes.textContent.match(/(\d+) ?Mi?B/);
      if (memoryLimitMatch && memoryLimitMatch[1]) {
        task.setMemoryLimit(parseInt(memoryLimitMatch[1], 10));
      }
    } else {
      console.warn('无法找到限制信息元素');
    }

    // 提取输入输出样例(增强版)
    const inputs = [...elem.querySelectorAll('h3')]
      .filter(el => el.textContent?.includes('入力例') || el.textContent?.includes('Sample Input'))
      .map(el => {
        const nextEl = el.nextElementSibling;
        if (!nextEl) return null;
        
        if (nextEl.tagName === 'PRE') return nextEl;
        if (nextEl.tagName === 'DIV') return nextEl.nextElementSibling;
        if (nextEl.children.length >= 3) return nextEl.children[2];
        return nextEl.children[0];
      })
      .filter(el => el !== null);

    const outputs = [...elem.querySelectorAll('h3')]
      .filter(el => el.textContent?.includes('出力例') || el.textContent?.includes('Sample Output'))
      .map(el => {
        const nextEl = el.nextElementSibling;
        if (!nextEl) return null;
        
        if (nextEl.tagName === 'PRE') return nextEl;
        if (nextEl.tagName === 'DIV') return nextEl.nextElementSibling;
        if (nextEl.children.length >= 3) return nextEl.children[2];
        return nextEl.children[0];
      })
      .filter(el => el !== null);

    for (let i = 0; i < inputs.length && i < outputs.length; i++) {
      task.addTest(inputs[i].textContent, outputs[i].textContent);
    }

    return task.build();
  } catch (error) {
    console.error(`解析AtCoder题目时出错: ${error.message}`, error);
    throw new Error(`解析AtCoder题目失败: ${error.message}`);
  }
}
src/parsers/problem/AtCoderProblemParser.ts(更新匹配模式)
public getMatchPatterns(): string[] {
  return [
    'https://atcoder.jp/contests/*/tasks/*',
    'https://atcoder.jp/contests/*/tasks/*?lang=ja',
    'https://atcoder.jp/contests/*/tasks/*?lang=en'
  ];
}

3. 重新构建扩展

npm install
npm run build

测试验证

测试用例设计

为确保修复有效,需要覆盖以下测试场景:

测试场景测试步骤预期结果
标准题目解析访问https://atcoder.jp/contests/abc123/tasks/abc123_a成功解析题目信息,无错误
带语言参数的题目访问https://atcoder.jp/contests/abc123/tasks/abc123_a?lang=en成功解析英文题目信息
交互式题目访问包含交互式任务标记的题目正确识别为交互式题目
特殊格式题目访问包含复杂样例输入输出的题目正确提取所有样例
竞赛任务列表访问https://atcoder.jp/contests/abc123/tasks成功批量解析所有题目

测试结果验证

修复后,解析流程应能正确处理各种情况,不再抛出TypeError。解析器会:

  1. 对所有DOM查询结果进行空值检查
  2. 提供更灵活的URL匹配模式
  3. 在遇到错误时给出明确提示
  4. 实现备选方案处理部分元素缺失的情况

总结与展望

通过增强代码的健壮性和错误处理能力,我们成功解决了Competitive Companion解析AtCoder题目时的TypeError问题。这个修复不仅解决了当前的问题,也提高了代码对未来网站结构变化的适应能力。

未来可以从以下方面进一步优化:

  1. 实现更灵活的页面解析策略,减少对特定DOM结构的依赖
  2. 添加用户自定义解析规则的功能
  3. 建立解析器自动测试系统,及时发现网站结构变化

这些改进将使Competitive Companion在面对各类在线判题系统的变化时更加稳定可靠,为程序设计竞赛选手提供更好的支持。

【免费下载链接】competitive-companion Browser extension which parses competitive programming problems 【免费下载链接】competitive-companion 项目地址: https://gitcode.com/gh_mirrors/co/competitive-companion

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值