SaveChatGPT

    @@ -1,252 +1,108 @@ -### Hiroshi Okada +/* + * @title SaveChatGPT + * @description ChatGPTのチャットをテキストファイルに + * @include http://* + * @license MIT License + * @require + */ + +(() => { + function getUserName(element) { + const imgElements = element.querySelectorAll("img[alt]"); + for (const imgElement of imgElements) { + const altText = imgElement.alt; + if (altText) { + return altText; + } + } + return "?"; + } + + function getAssistantName(assistantElement) { + let assistantName = assistantElement.textContent; + if (assistantName.length > 32) { + assistantName = assistantName.substring(0, 32) + '...'; + } + return assistantName; + } + + function processCodeBlocks(element) { + const codeBlocks = element.querySelectorAll("pre > div"); + for (const codeBlock of codeBlocks) { + const language = codeBlock.querySelector(":first-child > span").textContent; + const code = codeBlock.querySelector("code").textContent; + const formattedCode = `\n\n\`\`\`${language}\n${code}\n\`\`\`\n\n`; + + codeBlock.parentElement.innerHTML = formattedCode; + } + } + + function processListItem(element, indentLelvel = -1, isOL=false, startNumber = 1) { + if (element.nodeType !== Node.ELEMENT_NODE) { + return; + } + if (element.tagName === "UL") { + isOL = false; + indentLelvel += 1; + } else if (element.tagName === "OL") { + isOL = true; + startNumber = element.getAttribute("start") || 1; + indentLelvel += 1; + } + for(const childElement of element.children) { + processListItem(childElement, indentLelvel, isOL, startNumber); + } + if (element.tagName === "LI") { + const indent = " ".repeat(indentLelvel > 0 ? indentLelvel : 0); + const maker = isOL ? `${startNumber}. ` : "- "; + element.textContent = `\n\n${indent}${maker}${element.textContent}\n`; + } + return element; + } + + function generateMarkdown(elements) { + let markdownText = ""; + const lastIndex = elements.length - 2; + const assistantName = getAssistantName(elements[0].cloneNode(true)); + + for (let i = 1; i <= lastIndex; i++) { + const clonedElement = elements[i].cloneNode(true); + processCodeBlocks(clonedElement); + processListItem(clonedElement); + + const userName = getUserName(clonedElement); + const speaker = userName !== "?" ? userName : assistantName; + markdownText += `### ${speaker}\n\n`; + + markdownText += clonedElement.textContent; + + if (i !== lastIndex) { + markdownText += "\n\n---\n\n"; + } + } + + markdownText += `\n\n---\n\n${document.URL}\n` + + return markdownText; + } + + const elements = document.querySelector("main > div.flex-1 > div.h-full > div > div").children; + const markdownText = generateMarkdown(elements); + const now = new Date(); + const formattedDate = now.toISOString().replace(/[-T:.]/g, '_').slice(0, -5); + const pageTitle = (document.title || "chatgpt-conversation") + "_" + formattedDate; + const filename = `${pageTitle}.md`; + + const file = new Blob([markdownText], { + type: "text/plain" + }); + + const a = document.createElement("a"); + a.href = URL.createObjectURL(file); + a.download = filename; + a.click(); + a.remove(); +})(); -Python で下向き構文解析を行うライブラリを教えてください。 ---- -### Model: Default (GPT-3.5) - -Pythonで下向き構文解析を行うライブラリとしては、主に以下のものがあります。 - -1. PLY (Python Lex-Yacc) - - -- Pythonの標準ライブラリであるply.lexとply.yaccを使用して、構文解析を行うことができます。 - - -- LexとYaccの機能をPythonで実現しているため、Cで開発されたLexとYaccと似た使い方ができます。 - - -2. ANTLR (ANother Tool for Language Recognition) - - -- Javaで開発されたANTLRをPythonから使用することができます。 - - -- 構文解析器の生成や構文木の生成、構文解析エラーの処理などをサポートしています。 - - -3. PyParsing - - -- Pythonで構文解析を行うためのライブラリで、正規表現や文脈自由文法を使って、構文解析を行うことができます。 - - -- PLYやANTLRと比べると柔軟性が高く、小規模な言語の構文解析に向いています。 -以上のライブラリがありますが、その中でもPLYが標準的に使われることが多いです。 - ---- - -### Hiroshi Okada - -下向き構文解析に限らずEBNF等の文法表現を使って構文解析するライブラリを教えてください。 - ---- - -### Model: Default (GPT-3.5) - -以下は、PythonでEBNFなどの文法表現を使って構文解析を行うライブラリの例です。 - -1. PyParsing - - -- 上記でも紹介したライブラリで、EBNFを含む様々な文法表現をサポートしています。 - - -- PyParsingでは、構文規則を定義する際にはBNF(Backus-Naur Form)やEBNF(Extended Backus-Naur Form)を使うことができます。 - - -2. grako - - -- Pythonで書かれた構文解析器生成ツールで、EBNFを含む様々な文法表現をサポートしています。 - - -- grakoでは、EBNFを使って構文規則を定義することができます。 - - -3. Parsimonious - - -- Pythonで書かれた、PEG(Parsing Expression Grammar)を使った構文解析ライブラリです。 - - -- PEGはEBNFに似た文法表現で、パーサーを書きやすくするための特殊な構文規則を持っています。 -これらのライブラリは、Pythonでの構文解析を簡単に行うための強力なツールです。使用する際には、各ライブラリのドキュメントを参照することをおすすめします。 - ---- - -### Hiroshi Okada - -Lark はどうでしょうか。 - ---- - -### Model: Default (GPT-3.5) - -Larkは、Pythonで書かれた構文解析ライブラリで、EBNFと近い文法表現を採用しています。Larkは、パーサーを生成するためのDSL(Domain-Specific Language)を提供し、カスタム構文解析器を作成することができます。Larkは、以下のような特徴があります。 - -1. シンプルな文法記述: Larkでは、EBNFやBNFに似た構文規則を定義することができます。規則は、Pythonの関数やデコレーターを使って記述され、パーサーの生成に必要なAST(抽象構文木)の定義も可能です。 - - -1. パーサージェネレーション: Larkでは、構文規則からパーサーを生成するためのDSLが提供されています。生成されたパーサーはPythonのモジュールとして利用することができます。 - - -1. パーサーのカスタマイズ: Larkでは、生成されたパーサーをカスタマイズすることができます。具体的には、トークンの種類、パーサーの動作、エラー処理などをカスタマイズすることができます。 - - -1. Unicodeサポート: Larkは、Unicodeの文字列をサポートしています。UTF-8エンコードにも対応しており、多言語に対応したパーサーを作成することができます。 -Larkは、比較的新しいライブラリであり、簡単に使える文法記述と、高速なパーサージェネレーションを特徴としています。Larkは、中規模の言語の構文解析に向いており、構文解析の高速化が求められる場合にも有用です。 - ---- - -### Hiroshi Okada - -それぞれのライブラリを使って()と+/*= と数値のみを使って計算するプログラムを作成したときコードが短いのはどれですか。 - ---- - -### Model: Default (GPT-3.5) - -PyParsingは、文法を簡潔に書くことができ、構文解析の実装が簡単であるため、短いコードで計算プログラムを作成することができます。以下は、PyParsingを使って()と+/*= と数値のみを使って計算するプログラムを作成する例です。 - -```python -from pyparsing import ( - Forward, Group, Literal, Optional, Regex, Suppress, Word -) - -# 数値のパース用正規表現 -number = Regex(r'\d+(\.\d*)?') - -# 二項演算子の優先順位を定義する -precedence = [ - ('left', ['+', '-']), - ('left', ['*', '/']) -] - -# 式を定義する -expr = Forward() -atom = number | Suppress('(') + expr + Suppress(')') -term = atom + Optional(Literal('*') + atom) | atom + Optional(Literal('/') + atom) -expr << term + Optional(Literal('+') + term | Literal('-') + term) - -# 式の評価を行う関数を定義する -def eval_expression(s, loc, toks): - stack = [toks[0]] - for i in range(1, len(toks), 2): - operator = toks[i] - operand = toks[i + 1] - if operator == '+': - stack.append(operand) - elif operator == '-': - stack.append(-operand) - elif operator == '*': - stack[-1] *= operand - elif operator == '/': - stack[-1] /= operand - return stack[0] - -# 式の評価ルールを定義する -expr.setParseAction(eval_expression) - -# テスト用の式をパースする -test_expr = '3*(4+5)-2/2' -result = expr.parseString(test_expr)[0] - -print(f'{test_expr} = {result}') # 出力: 3*(4+5)-2/2 = 31.0 - -``` - -以上のように、PyParsingを使えば簡潔で読みやすいコードを書くことができます。ただし、解析速度が遅いため、大規模な構文解析には向いていません。 - ---- - -### Hiroshi Okada - -同様のものをLa を使うとどうなりますか。(字句解析の部分は不要です) - ---- - -### Model: Default (GPT-3.5) - -La(Lark)を使って、()と+/*= と数値のみを使って計算するプログラムを作成する例を示します。まず、Larkで使用する文法を定義します。以下は、EBNFと近い形式で表現された文法です。 - -```ebnf -?start: sum - -?sum: product - | sum "+" product -> add - | sum "-" product -> sub - -?product: atom - | product "*" atom -> mul - | product "/" atom -> div - -?atom: NUMBER - | "(" sum ")" - -%import common.NUMBER -%import common.WS -%ignore WS - -``` - -この文法に基づいて、パーサーを生成するためのPythonコードを書きます。 - -```python -from lark import Lark, Transformer - -# パーサー生成用の文法を定義する -grammar = """ - ?start: sum - - ?sum: product - | sum "+" product -> add - | sum "-" product -> sub - - ?product: atom - | product "*" atom -> mul - | product "/" atom -> div - - ?atom: NUMBER - | "(" sum ")" - - %import common.NUMBER - %import common.WS - %ignore WS -""" - -# パーサーを生成する -parser = Lark(grammar, parser="lalr") - - -# 式の評価用のTransformerを定義する -class Calculator(Transformer): - def add(self, values): - return values[0] + values[1] - - def sub(self, values): - return values[0] - values[1] - - def mul(self, values): - return values[0] * values[1] - - def div(self, values): - return values[0] / values[1] - - def atom(self, values): - return float(values[0]) - -# テスト用の式をパースする -test_expr = '3*(4+5)-2/2' -result = Calculator().transform(parser.parse(test_expr)) - -print(f'{test_expr} = {result}') # 出力: 3*(4+5)-2/2 = 31.0 - -``` - -Larkでは、EBNFやPEGのような形式で文法を表現することができます。この文法を基にして、Larkはパーサーを自動的に生成します。また、パーサーの生成時には、パーサージェネレーターとしてEarleyやLALRを選択することができます。パースの結果を簡単に変換するために、Transformerを使用することができます。La(Lark)は、EBNFなどの文法表現に慣れている人にとって、より自然で直感的な文法の定義ができるため、コードの可読性が高いと言えます。 - ---- - -https://chat.openai.com/chat/2058ee42-6db8-446c-aee3-df342c55dd27
  • /*
     * @title SaveChatGPT
     * @description ChatGPTのチャットをテキストファイルに
     * @include http://*
     * @license MIT License
     * @require 
     */
    
    (() => {
      function getUserName(element) {
        const imgElements = element.querySelectorAll("img[alt]");
        for (const imgElement of imgElements) {
          const altText = imgElement.alt;
          if (altText) {
            return altText;
          }
        }
        return "?";
      }
    
      function getAssistantName(assistantElement) {
        let assistantName = assistantElement.textContent;
        if (assistantName.length > 32) {
          assistantName = assistantName.substring(0, 32) + '...';
        }
        return assistantName;
      }
    
      function processCodeBlocks(element) {
        const codeBlocks = element.querySelectorAll("pre > div");
        for (const codeBlock of codeBlocks) {
          const language = codeBlock.querySelector(":first-child > span").textContent;
          const code = codeBlock.querySelector("code").textContent;
          const formattedCode = `\n\n\`\`\`${language}\n${code}\n\`\`\`\n\n`;
    
          codeBlock.parentElement.innerHTML = formattedCode;
        }
      }
    
      function processListItem(element, indentLelvel = -1, isOL=false, startNumber = 1) {
        if (element.nodeType !== Node.ELEMENT_NODE) {
          return;
        }
        if (element.tagName === "UL") {
          isOL = false;
          indentLelvel += 1;
        } else if (element.tagName === "OL") {
          isOL = true;
          startNumber = element.getAttribute("start") || 1;
          indentLelvel += 1;
        }
        for(const childElement of element.children) {
          processListItem(childElement, indentLelvel, isOL, startNumber);
        }
        if (element.tagName === "LI") {
          const indent = "  ".repeat(indentLelvel > 0 ? indentLelvel : 0);
          const maker = isOL ? `${startNumber}. ` : "- ";
          element.textContent = `\n\n${indent}${maker}${element.textContent}\n`;
        }
        return element;
      }
    
      function generateMarkdown(elements) {
        let markdownText = "";
        const lastIndex = elements.length - 2;
        const assistantName = getAssistantName(elements[0].cloneNode(true));
    
        for (let i = 1; i <= lastIndex; i++) {
          const clonedElement = elements[i].cloneNode(true);
          processCodeBlocks(clonedElement);
          processListItem(clonedElement);
    
          const userName = getUserName(clonedElement);
          const speaker = userName !== "?" ? userName : assistantName;
          markdownText += `### ${speaker}\n\n`;
    
          markdownText += clonedElement.textContent;
    
          if (i !== lastIndex) {
            markdownText += "\n\n---\n\n";
          }
        }
    
        markdownText += `\n\n---\n\n${document.URL}\n`
    
        return markdownText;
      }
    
      const elements = document.querySelector("main > div.flex-1 > div.h-full > div > div").children;
      const markdownText = generateMarkdown(elements);
      const now = new Date();
      const formattedDate = now.toISOString().replace(/[-T:.]/g, '_').slice(0, -5);
      const pageTitle = (document.title || "chatgpt-conversation") + "_" + formattedDate;
      const filename = `${pageTitle}.md`;
    
      const file = new Blob([markdownText], {
        type: "text/plain"
      });
    
      const a = document.createElement("a");
      a.href = URL.createObjectURL(file);
      a.download = filename;
      a.click();
      a.remove();
    })();
    
    
    
    
  • Permalink
    このページへの個別リンクです。
    RAW
    書かれたコードへの直接のリンクです。
    Packed
    文字列が圧縮された書かれたコードへのリンクです。
    Userscript
    Greasemonkey 等で利用する場合の .user.js へのリンクです。
    Loader
    @require やソースコードが長い場合に多段ロードする Loader コミのコードへのリンクです。
    Metadata
    コード中にコメントで @xxx と書かれたメタデータの JSON です。

History

  1. 2023/03/23 16:43:36 - 2023-03-23
  2. 2023/03/23 16:39:44 - 2023-03-23
  3. 2023/03/23 16:39:21 - 2023-03-23
  4. 2023/03/21 20:07:51 - 2023-03-21
  5. 2023/03/21 12:06:21 - 2023-03-21