spoiled competitive programming

Parse AtCoder problem statement into sections and create Anki CSV

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

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

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

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