[PYTHON] J'ai créé un outil pour générer du Markdown à partir du fichier JSON Scrapbox exporté

introduction

"Qu'en est-il d'avoir été ingénieur pendant cinq ans et de ne produire aucune production?" J'ai ressenti un sentiment de crise, alors j'ai décidé de le poster sur Qiita. Il peut être difficile à lire car c'est le premier article, mais pardonnez-moi s'il vous plaît.

Aperçu

J'ai décidé d'utiliser Scrapbox pour les activités internes, mais j'ai finalement voulu mettre les pages dans Scrapbox sur un serveur de fichiers afin de pouvoir le laisser comme un actif interne. Scrapbox a une fonction pour exporter le contenu de toutes les pages sous forme de fichier JSON, mais il est difficile à lire tel quel. J'ai donc cherché un outil qui le convertirait en Markdown et l'enregistrerait, mais je n'ai pas trouvé d'outil qui avait l'air bien, alors je l'ai fait moi-même en utilisant Python.

Le fichier JSON exporté a ce format. (Exportation sans métadonnées.)

john-project.json


{
  "name": "john-project",
  "displayName": "john-project",
  "exported": 1598595295,
  "pages": [
    {
      "title": "Comment utiliser Scrapbox",
      "created": 1598594744,
      "updated": 1598594744,
      "id": "000000000000000000000000",
      "lines": [
        "Comment utiliser Scrapbox",
        "Bienvenue dans Scrapbox. Vous pouvez librement modifier et utiliser cette page.",
        "",
        "Invitez des membres à participer à ce projet",
        //Omission
        " [Nous publions des cas d'utilisation d'entreprises https://scrapbox.io/case]",
        ""
      ]
    },
    {
      "title": "La première ligne est le titre",
      "created": 1598594777,
      "updated": 1598595231,
      "id": "111111111111111111111111",
      "lines": [
        "La première ligne est le titre",
        "[**Deux astérisques sont le style de titre]",
        "Liste à puces avec retrait",
        " \Si vous augmentez le nombre de t",
        " [[Audacieux]]Ou[/ italic]、[-Ligne d'annulation]Peut être utilisé",
        " \t comme ça[-/* italic]Peut être combiné",
        "[Lien de page]Ou[Lien externe https://scrapbox.io]",
        "code:test.py",
        " for i in range(5):",
        "     print('[*Ignorer les blocs de code internes]')",
        "",
        "`[- code]`Ignorer",
        "",
        "table:Format tabulaire",
        " aaa\tbbb\tccc",
        "Ah ah\t\t Uuu",
        "\t111\t222\t333",
        ""
      ]
    }
  ]
}

À l'aide de l'outil que vous avez créé, il sera converti en un fichier Markdown comme celui ci-dessous. (* Afin d'améliorer l'apparence sur Qiita, il y a une partie où un espace pleine largeur est ajouté plus tard au bloc de code et à la fin de la ligne du tableau)

``La première ligne est le titre.md`


#La première ligne est le titre
###Deux astérisques sont le style de titre
-Liste à puces avec retrait
  -Si vous augmentez le nombre, les caractères seront encore abaissés
- **Audacieux**Ou_italic_ 、 ~~Ligne d'annulation~~Peut être utilisé
  -de cette façon_~~**italic**~~_Peut être combiné
[Lien de page]()Ou[Lienexterne](https://scrapbox.io)
code:test.py
``` 
 for i in range(5):
 print ('[* Ignorer les blocs de code internes]')
``` 

`[- code]`Ignorer

table:Format tabulaire
|aaa|bbb|ccc| 
|-----|-----|-----|-----|
|Ah ah||Uuu|
|111|222|333|


L'apparence est convertie comme suit. Les sauts de ligne sont un peu lâches, mais c'est assez facile à voir. scrapbox_sample.png

最初の行は見出し.png

politique

De nombreux membres (y compris moi-même) sont nouveaux sur Scrapbox, et il semble qu'ils ne soient pas très élaborés, j'ai donc décidé de convertir uniquement les notations que je pourrais utiliser sans viser une conversion parfaite. La méthode de conversion est simple, utilisez simplement des expressions régulières pour trouver les parties écrites en notation Scrapbox et remplacez-les par le format Markdown. Enfin, exécutez-le pour qu'il puisse être utilisé par des personnes qui n'ont pas installé Python.

environnement

J'ai utilisé Windows10 et Python3.7.

la mise en oeuvre

Lecture de fichier

Assurez-vous de recevoir le nom de fichier JSON comme premier argument. En faisant cela, vous pouvez l'utiliser en faisant simplement glisser et déposer le fichier JSON sur le fichier exe-ized. Créez également un dossier pour afficher Markdown.

filename = sys.argv[1]
with open(filename, 'r', encoding='utf-8') as fr:
    sb = json.load(fr)
    outdir = 'markdown/'
    if not os.path.exists(outdir):
        os.mkdir(outdir)

conversion

À partir de là, chaque page et chaque ligne seront converties dans l'ordre. Écrivez la cible de conversion entre () de chaque titre.

Titre (première ligne)

Scrapbox interprète la première ligne comme un titre, alors ajoutez `# '(espace net + demi-largeur) au début de la première ligne pour en faire un titre.

for p in sb['pages']:
    title = p['title']
    lines = p['lines']
    is_in_codeblock = False
    with open(f'{outdir}{title}.md', 'w', encoding='utf-8') as fw:
        for i, l in enumerate(lines):
            if i == 0:
                l = '# ' + l

Bloc de code (`` code: hoge.ext '')

Dans Scrapbox, les blocs de code peuvent être représentés par code: hoge.ext. Tant que le début de la ligne est vide, le bloc de code continue. Je ne veux pas convertir à l'intérieur du bloc de code, donc je vais continuer tout en déterminant si la ligne que je regarde est à l'intérieur du bloc de code. Notation Markdown lors de l'entrée et de la sortie d'un bloc de code```Ajouter.

# Traitement des blocs de code
if l.startswith('code:'):
    is_in_codeblock = True
    l += f'\n```'
elif is_in_codeblock and not l.startswith(('\t', ' ', ' ')):
    is_in_codeblock = False
    fw.write('```\n')

# Omission

# Convertir sinon un bloc de code
if not is_in_codeblock:
    l = convert(l)

####table(`table:hoge`

Dans Scrapboxtable:hogeLe tableau peut être exprimé avec. Le tableau continue tant que la ligne commence par un espace. Les tables Scrapbox n'ont pas d'en-têtes, mais Markdown ne peut pas représenter des tables sans en-têtes, donc il force la première ligne à être interprétée comme un en-tête. Les cellules sont séparées par des tabulations, donc|Convertir en. Les blancs au début d'une ligne peuvent avoir des tabulations, des espaces demi-largeur et des espaces pleine largeur, de sorte qu'ils seront convertis en boueux.

if l.startswith('table:'):
    is_in_table = True
elif is_in_table and not l.startswith(('\t', ' ', ' ')):
    is_in_table = False
if is_in_table:
    row += 1
    if row != 0:
         l = l.replace('\t', '|') + '|'
        if l.startswith(' '):
            l = l.replace(' ', '|', 1)
    if row == 1:
        col = l.count('|')
         l += f'\n{"|-----" * col}|'

####code(`hoge`

Comme je ne veux pas convertir le code, j'ai mis un processus pour supprimer la partie de code avant le processus de conversion de chaque notation. Il est écrit de la même manière que Markdown, vous pouvez donc simplement le supprimer.

def ignore_code(l: str) -> str:
    for m in re.finditer(r'`.+?`', l):
        l = l.replace(m.group(0), '')
    return l

####hashtag(#hoge

Si cela est écrit au début de la chaîne, il peut être interprété comme un titre par Markdown (cela semble différent selon le spectateur). pour cette raison,`Il est traité comme un code en l'enfermant dans.

def escape_hash_tag(l: str) -> str:
    for m in re.finditer(r'#(.+?)[ \t]', ignore_code(l)):
        l = l.replace(m.group(0), '`' + m.group(0) + '`')
 if l.startswith ('#'): ligne # 1 pour toutes les balises
        l = '`' + l + '`'
    return l

####Liste à puces (retrait)

Le nombre de retraits est compté et remplacé par le format Markdown.

def convert_list(l: str) -> str:
    m = re.match(r'[ \t ]+', l)
    if m:
        l = l.replace(m.group(0),
                      (len(m.group(0)) - 1) * '  ' + '- ', 1)
    return l

####Audacieux ([[hoge]][** hoge][*** hoge]

Dans Scrapbox[[hoge]]Ou[* hoge]Si vous aimez, ce sera audacieux. Aussi, dans cette dernière notation[** hoge]Si vous augmentez l'astérisque comme dans, les caractères deviendront plus grands.

Parmi ces dernières notations, les notations avec deux et trois astérisques ont été utilisées comme des en-têtes Markdown, je les ai donc converties en conséquence. En dehors de cela, il peut être utilisé en même temps que d'autres décorations, il sera donc converti séparément.

def convert_bold(l: str) -> str:
    for m in re.finditer(r'\[\[(.+?)\]\]', ignore_code(l)):
        l = l.replace(m.group(0), '**' + m.group(1) + '**')
 m = re.match (r '\ [(\ * \ * | \ * \ * \ *) (. +?) \]', Igno_code (l)) # Probablement l'en-tête
    if m:
        l = '#' * (5 - len(m.group(1))) + ' ' + \
 m.group (2) #Scrapbox a plus *
    return l

####Décoration de personnage ([* hoge][/ hoge][- hoge][-/* hoge]etc)

Dans Scrapbox, en plus de gras, diagonale[/ hoge]Et ligne d'annulation[- hoge]Peut être utilisé. Ceux-ci sont combinés[-/* hoge]Puisqu'il peut être utilisé comme, il traite en même temps.

def convert_decoration(l: str) -> str:
    for m in re.finditer(r'\[([-\*/]+) (.+?)\]', ignore_code(l)):
        deco_s, deco_e = ' ', ' '
        if '/' in m.group(0):
            deco_s += '_'
            deco_e = '_' + deco_e
        if '-' in m.group(0):
            deco_s += '~~'
            deco_e = '~~' + deco_e
        if '*' in m.group(0):
            deco_s += '**'
            deco_e = '**' + deco_e
        l = l.replace(m.group(0), deco_s + m.group(2) + deco_e)
    return l

(Le point culminant est étrange, mais je n'ai pas pu le réparer)

####Lien([Titre de l'URL][URL du titre][hoge]

Dans Scrapbox[Titre de l'URL]Ou[URL du titre]Exprimez le lien vers l'extérieur avec. Ne pense pas à la chose exactehttpJ'ai décidé d'interpréter celui commençant par comme une URL. Aussi,[hoge]Un format comme celui-ci est un lien vers une autre page dans Scrapbox. Ce lien ne peut pas être utilisé après la sortie Markdown, mais derrière()En ajoutant, seule l'apparence est comme un lien.

def convert_link(l: str) -> str:
    for m in re.finditer(r'\[(.+?)\]', ignore_code(l)):
        tmp = m.group(1).split(' ')
        if len(tmp) == 2:
            if tmp[0].startswith('http'):
                link, title = tmp
            else:
                title, link = tmp
            l = l.replace(m.group(0), f'[{title}]({link})')
        else:
            l = l.replace(m.group(0), m.group(0) + '()')
    return l

###conversion exe

Enfin, utilisez pyinstaller pour exe. Créez un fichier exe sans affichage de la console.

pip install pyinstaller
pyinstaller sb2md.py -wF

Faites glisser le fichier JSON vers le fichier exe&Vous pouvez exécuter le programme en le supprimant.

##finalement

Le code créé cette fois estGitHubIl est placé dans. Lors de l'écriture d'un si petit processus, je trouve toujours Python utile.

J'ai commencé à utiliser Scrapbox l'autre jour, et je ne suis pas très doué pour le moment, donc je prévois de le mettre à jour dès qu'une autre utilisation sortira.

Recommended Posts

J'ai créé un outil pour générer du Markdown à partir du fichier JSON Scrapbox exporté
J'ai créé un outil pour générer automatiquement un simple diagramme ER à partir de l'instruction CREATE TABLE
J'ai créé un plugin pour générer une table Markdown à partir de csv avec Vim
J'ai fait une commande pour marquer le clip de la table
J'ai créé un outil pour créer un nuage de mots à partir de wikipedia
J'ai créé un fichier de sous-titres (SRT) à partir des données JSON d'AmiVoice
J'ai fait un programme pour vérifier la taille d'un fichier avec Python
Comment générer un objet Python à partir de JSON
J'ai créé un outil pour compiler nativement Hy
J'ai créé un outil pour obtenir de nouveaux articles
J'ai fait un outil pour estimer le temps d'exécution de cron (+ débuts de PyPI)
J'ai créé un outil pour sauvegarder automatiquement les métadonnées de l'organisation Salesforce
J'ai créé un package pour créer un fichier exécutable à partir du code source Hy
Je veux voir le nom de fichier de DataLoader
Script Python qui crée un fichier JSON à partir d'un fichier CSV
[Python] J'ai créé un système pour introduire "la recette que je veux vraiment" depuis le site de recettes!
J'ai fait une fonction pour vérifier le modèle de DCGAN
[Titan Craft] J'ai créé un outil pour invoquer un géant sur Minecraft
Je vous ai fait exécuter des commandes depuis un navigateur WEB
Création d'un toolver qui crache le système d'exploitation, Python, les modules et les versions d'outils à Markdown
J'ai créé un script en Python pour convertir un fichier texte pour JSON (pour l'extrait d'utilisateur vscode)
J'ai créé un outil pour obtenir les liens de réponse d'OpenAI Gym en même temps
J'ai essayé de faire un programme pour résoudre (indice) la recherche d'erreur de Saiseriya
J'ai essayé de couper une image fixe de la vidéo
J'ai essayé de créer un outil d'échafaudage pour le framework Web Python Bottle
J'ai fait une commande pour afficher un calendrier coloré dans le terminal
J'ai créé un outil pour parcourir automatiquement plusieurs sites avec Selenium (Python)
Je souhaite envoyer un signal uniquement du sous-thread au thread principal
[Django] a créé un champ pour saisir des dates avec des nombres à 4 chiffres
J'ai fait une minuterie de cuisine à afficher sur la barre d'état!
Je souhaite recevoir le fichier de configuration et vérifier si le fichier JSON généré par jinja2 est un JSON valide
J'ai créé un script en python pour convertir des fichiers .md au format Scrapbox
J'ai fait un script pour afficher des pictogrammes
J'ai fait une simple minuterie qui peut être démarrée depuis le terminal
J'ai créé un konoha de bibliothèque qui fait passer le tokenizer à une belle sensation
J'ai créé un outil pour convertir Jupyter py en ipynb avec VS Code
J'ai créé un outil d'estampage automatique du navigateur.
J'ai créé un fichier de configuration avec Python
J'ai créé une fonction pour voir le mouvement d'un tableau à deux dimensions (Python)
J'ai créé un script pour vérifier si l'anglais est entré dans la position spécifiée du fichier JSON en Python.
[LINE Messaging API] Je souhaite envoyer un message du programme à tout le monde LINE
J'ai créé une bibliothèque pour faire fonctionner la pile AWS CloudFormation à partir de CUI (Python Fabric)
J'ai créé un outil pour informer Slack des événements Connpass et en ai fait Terraform
Je viens de créer un outil pour afficher facilement les données sous forme de graphique par opération GUI
Je veux créer un lecteur de musique et enregistrer de la musique en même temps
J'ai créé une commande appdo pour exécuter des commandes dans le contexte de l'application
J'ai essayé de générer automatiquement une table de gestion des ports à partir de L2SW Config
Je ne pouvais pas m'échapper du futon, alors j'ai fabriqué une machine à éplucher les futons entièrement automatique.
[Python] J'ai essayé d'obtenir le nom du type sous forme de chaîne de caractères à partir de la fonction type
J'ai créé un programme pour rechercher des mots sur la fenêtre (développement précédent)
J'ai fait un script pour enregistrer la fenêtre active en utilisant win32gui de Python
J'ai créé un outil utile pour Digital Ocean
Comment créer un fichier JSON en Python
J'ai créé un outil de collecte de configuration de routeur Config Collecor
Enregistrer l'objet dans un fichier avec pickle
J'ai essayé de générer une chaîne de caractères aléatoire
Je veux écrire dans un fichier avec Python
Création d'un outil pour générer un diagramme de séquence à partir d'un fichier de capture de paquets de plusieurs nœuds