- // ==UserScript==
- // @name spoiled competitive programming
- // @namespace http://tampermonkey.net/
- // @version 0.3
- // @description Parse AtCoder problem statement into sections and create Anki CSV
- // @author Harui (totally helped by GPT-4o)
- // @match https://atcoder.jp/contests/*/tasks/*
- // @grant none
- // @license MIT
- // ==/UserScript==
-
- (function() {
- 'use strict';
-
- // Create buttons for Japanese
- const openMarkdownButtonJa = document.createElement('button');
- openMarkdownButtonJa.textContent = '新しいタブで日本語のMarkdownを開く';
- openMarkdownButtonJa.style.position = 'fixed';
- openMarkdownButtonJa.style.left = '10px';
- openMarkdownButtonJa.style.bottom = '145px';
- openMarkdownButtonJa.style.zIndex = '1000';
- openMarkdownButtonJa.style.padding = '10px';
- openMarkdownButtonJa.style.backgroundColor = '#4CAF50';
- openMarkdownButtonJa.style.color = 'white';
- openMarkdownButtonJa.style.border = 'none';
- openMarkdownButtonJa.style.borderRadius = '5px';
- openMarkdownButtonJa.style.cursor = 'pointer';
-
- const openHtmlButtonJa = document.createElement('button');
- openHtmlButtonJa.textContent = '新しいタブで日本語のHTMLを開く';
- openHtmlButtonJa.style.position = 'fixed';
- openHtmlButtonJa.style.left = '10px';
- openHtmlButtonJa.style.bottom = '100px';
- openHtmlButtonJa.style.zIndex = '1000';
- openHtmlButtonJa.style.padding = '10px';
- openHtmlButtonJa.style.backgroundColor = '#4CAF50';
- openHtmlButtonJa.style.color = 'white';
- openHtmlButtonJa.style.border = 'none';
- openHtmlButtonJa.style.borderRadius = '5px';
- openHtmlButtonJa.style.cursor = 'pointer';
-
- // Append the buttons to the body
- document.body.appendChild(openMarkdownButtonJa);
- document.body.appendChild(openHtmlButtonJa);
-
- // Event listener for open Markdown button (Japanese)
- openMarkdownButtonJa.addEventListener('click', () => {
- const markdownContent = htmlToMarkdown(extractHtml('ja'));
-
- // Open the Markdown content in a new tab
- const newTab = window.open();
- newTab.document.open();
- newTab.document.write('<pre>' + markdownContent + '</pre>');
- newTab.document.close();
- });
-
- // Event listener for open HTML button (Japanese)
- openHtmlButtonJa.addEventListener('click', () => {
- const htmlContent = htmlToAnkiHtml(extractHtml('ja'));
-
- // Open the HTML content in a new tab
- const newTab = window.open();
- newTab.document.open();
- newTab.document.write(htmlContent);
- newTab.document.close();
- });
-
- // Create buttons and style them
- const openMarkdownButtonEn = document.createElement('button');
- openMarkdownButtonEn.textContent = 'Open English Markdown in New Tab';
- openMarkdownButtonEn.style.position = 'fixed';
- openMarkdownButtonEn.style.left = '10px';
- openMarkdownButtonEn.style.bottom = '55px';
- openMarkdownButtonEn.style.zIndex = '1000';
- openMarkdownButtonEn.style.padding = '10px';
- openMarkdownButtonEn.style.backgroundColor = '#4CAF50';
- openMarkdownButtonEn.style.color = 'white';
- openMarkdownButtonEn.style.border = 'none';
- openMarkdownButtonEn.style.borderRadius = '5px';
- openMarkdownButtonEn.style.cursor = 'pointer';
-
- const openHtmlButtonEn = document.createElement('button');
- openHtmlButtonEn.textContent = 'Open English HTML in New Tab';
- openHtmlButtonEn.style.position = 'fixed';
- openHtmlButtonEn.style.left = '10px';
- openHtmlButtonEn.style.bottom = '10px';
- openHtmlButtonEn.style.zIndex = '1000';
- openHtmlButtonEn.style.padding = '10px';
- openHtmlButtonEn.style.backgroundColor = '#4CAF50';
- openHtmlButtonEn.style.color = 'white';
- openHtmlButtonEn.style.border = 'none';
- openHtmlButtonEn.style.borderRadius = '5px';
- openHtmlButtonEn.style.cursor = 'pointer';
-
- // Append the buttons to the body
- document.body.appendChild(openMarkdownButtonEn);
- document.body.appendChild(openHtmlButtonEn);
-
- // Create button for Anki CSV download
- const downloadCsvButton = document.createElement('button');
- downloadCsvButton.textContent = 'Anki用CSVをダウンロード';
- downloadCsvButton.style.position = 'fixed';
- downloadCsvButton.style.left = '10px';
- downloadCsvButton.style.bottom = '190px';
- downloadCsvButton.style.zIndex = '1000';
- downloadCsvButton.style.padding = '10px';
- downloadCsvButton.style.backgroundColor = '#4CAF50';
- downloadCsvButton.style.color = 'white';
- downloadCsvButton.style.border = 'none';
- downloadCsvButton.style.borderRadius = '5px';
- downloadCsvButton.style.cursor = 'pointer';
-
- // Append the button to the body
- document.body.appendChild(downloadCsvButton);
-
- // Event listener for download CSV button
- downloadCsvButton.addEventListener('click', () => {
- const htmlContent = htmlToAnkiHtml(extractHtml('ja'));
- const csvContent = generateAnkiCsv(htmlContent);
-
- // Create a blob from the CSV content
- const blob = new Blob([csvContent], { type: 'text/csv' });
- const url = URL.createObjectURL(blob);
-
- // Create a link element and trigger the download
- const a = document.createElement('a');
- a.href = url;
- a.download = 'anki.csv';
- document.body.appendChild(a);
- a.click();
- document.body.removeChild(a);
- URL.revokeObjectURL(url);
- });
-
- // Function to extract parts and remove Katex, returning HTML
- function extractHtml(lang) {
- const parts = document.querySelectorAll(`span.lang-${lang}`);
- const problemTitle = document.title;
-
- let htmlContent = `<h1>${problemTitle}</h1>\n\n`;
-
- parts.forEach(part => {
- // Clone the part to avoid modifying the original document
- const clone = part.cloneNode(true);
-
- // Remove "Copy" buttons
- const copyButtons = clone.querySelectorAll('.btn-copy, .btn-pre, .div-btn-copy');
- copyButtons.forEach(button => button.remove());
-
- // Remove Katex elements
- const katexElements = clone.querySelectorAll('.katex');
- katexElements.forEach(katex => {
- const tex = katex.querySelector('.katex-mathml annotation');
- if (tex) {
- const textNode = document.createTextNode(tex.textContent);
- katex.parentNode.replaceChild(textNode, katex);
- }
- });
-
- // Append HTML content
- htmlContent += clone.outerHTML + '\n\n';
- });
-
- return htmlContent;
- }
-
- // Function to generate Anki CSV
- function generateAnkiCsv(htmlContent) {
- const problemId = window.location.pathname.split('/')[2];
- const problemAlpha = window.location.pathname.split('/')[4].split('_')[1];
- const problemTitle = document.querySelector('title').textContent.trim();
- const problemName = `${problemId.toUpperCase()}_${problemAlpha.toUpperCase()} ${problemTitle}`;
- const problemUrl = window.location.href;
-
- // Replace double quotes with double double quotes
- const escapedHtmlContent = htmlContent.replace(/"/g, '""');
-
- const csvContent = `#separator:tab\n#html:true\n"${problemName}"\t"<a href=""${problemUrl}"">${problemUrl}</a>"\t"${escapedHtmlContent}"`;
- return csvContent;
- }
-
-
- // Event listener for open Markdown button (English)
- openMarkdownButtonEn.addEventListener('click', () => {
- const markdownContent = htmlToMarkdown(extractHtml('en'));
-
- // Open the Markdown content in a new tab
- const newTab = window.open();
- newTab.document.open();
- newTab.document.write('<pre>' + markdownContent + '</pre>');
- newTab.document.close();
- });
-
- // Event listener for open HTML button (English)
- openHtmlButtonEn.addEventListener('click', () => {
- const htmlContent = extractHtml('en');
-
- // Open the HTML content in a new tab
- const newTab = window.open();
- newTab.document.open();
- newTab.document.write(htmlContent);
- newTab.document.close();
- });
-
- // Function to convert HTML to Markdown
- function htmlToMarkdown(html) {
- // Simple conversion rules
- const rules = [
- { regex: /<h3>(.*?)<\/h3>/g, replacement: '\n### $1\n' },
- { regex: /<h2>(.*?)<\/h2>/g, replacement: '\n## $1\n' },
- { regex: /<h1>(.*?)<\/h1>/g, replacement: '\n# $1\n' },
- { regex: /<p>(.*?)<\/p>/g, replacement: '$1\n' },
- { regex: /<ul>(.*?)<\/ul>/gs, replacement: '$1' },
- { regex: /<li>(.*?)<\/li>/g, replacement: '- $1' },
- { regex: /<pre.*?>(.*?)<\/pre>/gs, replacement: '\n\n``` \n$1\n```' },
- { regex: /<var>(.*?)<\/var>/g, replacement: '`$1`' },
- { regex: /<div.*?>(.*?)<\/div>/gs, replacement: '$1' },
- { regex: /<span.*?>(.*?)<\/span>/g, replacement: '$1' },
- { regex: /<section.*?>(.*?)<\/section>/gs, replacement: '$1' },
- { regex: /<hr>/g, replacement: '---' },
- { regex: /<br>/g, replacement: '\n' }
- ];
-
- // Apply rules
- let markdown = html;
- rules.forEach(rule => {
- markdown = markdown.replace(rule.regex, rule.replacement);
- });
-
- // Remove any remaining HTML tags
- markdown = markdown.replace(/<\/?[^>]+(>|$)/g, "");
-
- return markdown.trim();
- }
-
- // Function to convert HTML to Anki HTML
- function htmlToAnkiHtml(html) {
- // Simple conversion rules
- const rules = [
- { regex: /<h3>(.*?)<\/h3>/g, replacement: '\n<h3>$1</h3>\n' },
- { regex: /<h2>(.*?)<\/h2>/g, replacement: '\n<h2>$1</h2>\n' },
- { regex: /<h1>(.*?)<\/h1>/g, replacement: '\n<h1>$1</h1>\n' },
- { regex: /<p>(.*?)<\/p>/g, replacement: '<p>$1</p>\n' },
- { regex: /<ul>(.*?)<\/ul>/gs, replacement: '$1' },
- { regex: /<li>(.*?)<\/li>/g, replacement: '<li>$1</li>' },
- { regex: /<pre.*?>(.*?)<\/pre>/gs, replacement: '\n\n<pre>\n$1\n</pre>' },
- { regex: /<var>(.*?)<\/var>/g, replacement: '<var>$1</var>' },
- { regex: /<div.*?>(.*?)<\/div>/gs, replacement: '<div>$1</div>' },
- { regex: /<span.*?>(.*?)<\/span>/g, replacement: '<span><anki-mathjax>$1</anki-mathjax></span>' },
- { regex: /<section.*?>(.*?)<\/section>/gs, replacement: '<section>$1</section>' },
- { regex: /<hr>/g, replacement: '<hr>' },
- { regex: /<br>/g, replacement: '<br>' },
- { regex: /\$(.*?)\$/g, replacement: '$1' } // Convert TeX to Anki TeX
- ];
-
- // Apply rules
- let ankiHtml = html;
- rules.forEach(rule => {
- ankiHtml = ankiHtml.replace(rule.regex, rule.replacement);
- });
-
- return ankiHtml.trim();
- }
- })();