spoiled competitive programming

Parse AtCoder problem statement into sections and create Anki CSV

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

You will need to install an extension such as Tampermonkey to install this script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

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