Lire la source Python-Markdown: Comment créer un analyseur

Objectif

J'ai décidé d'apprendre comment la bibliothèque d'analyseur de démarques est implémentée. (Brusque) On dit que la conception (modèle de conception) de l'analyseur dom de Java est magnifique, mais j'ai d'abord cherché une bibliothèque Python à laquelle je suis habitué.

Au fait, c'est Java. https://www.tutorialspoint.com/java_xml/java_dom_parse_document.htm

Cette fois, je vais lire la source de la bibliothèque suivante.

Python-Markdown https://github.com/Python-Markdown

Il semble que vous puissiez convertir en Markdown-> HTML. Bien sûr, nous analysons Markdown à l'intérieur, alors voyons de quel type de design il s'agit!

Si vous trouvez quelque chose qui ne va pas dans votre compréhension, veuillez le signaler ...!

Remarque

Il semble que les fonctions de base soient rassemblées sous Python-Markdown / markdown / markdown /. Comme ce sera gênant à l'avenir, les fichiers de ce répertoire seront abrégés en sample.py.

De plus, le code source publié ci-dessous est essentiellement un extrait des seules parties nécessaires (+ commenter et rédiger un mémo).

La partie principale est d'abord gâtée

L'essentiel est que chaque processeur qui détecte un élément particulier a été activé bloc par bloc. La cible sur laquelle agir est chaque bloc du texte original divisé par "\ n \ n". C'est, par exemple

<b>Balise incomplète, mais en gras

Pas en gras</b>

Si une ligne vide est insérée (= "\ n \ n" apparaît), la plage effective de l'élément sera coupée.

Dans cet exemple, d'abord

["<b>Balise incomplète, mais en gras", "Pas en gras</b>"」

Pour chaque bloc comme, d'abord la balise " <b> est incomplète, mais pour le gras ", les processeurs qui détectent chaque élément agissent dans l'ordre, et passent au bloc suivant ... Je pense que ce sera le flux.

Flux de processus

Au cœur de l'interface utilisateur de cette bibliothèque se trouvent la classe Markdown suivante et sa méthode convert.

core.py


class Markdown:

    #Convertir en html ici
    def convert(self, source):
        # source :Texte de démarque

Il y a un commentaire sur la méthode convert, et il ressemble à ceci une fois traduit en japonais.

  1. les préprocesseurs convertissent le texte Analyser l'élément de structure de haut niveau du texte prétraité en 2, 1 dans l'arborescence des éléments
  1. Les processeurs d'arborescence traitent l'arborescence des éléments. Par exemple, Inline Patterns trouve des éléments en ligne
  2. Faites fonctionner certains post-processeurs sur le texte sérialisé ElementTree.
  3. Écrivez le résultat dans une chaîne de caractères

Le texte original peut être plus facile à lire w

Étape 1 Préprocesseurs

core.py



class Markdown:

    def convert(self, source):
        <Abréviation>
        self.lines = source.split("\n")
        for prep in self.preprocessors:
            self.lines = prep.run(self.lines)

Commencez par diviser dans chaque ligne et laissez le prétraitement mordre. Les "préprocesseurs" sont obtenus ci-dessous.

preprocessors.py



def build_preprocessors(md, **kwargs):
    """ Build the default set of preprocessors used by Markdown. """
    preprocessors = util.Registry()
    preprocessors.register(NormalizeWhitespace(md), 'normalize_whitespace', 30)
    preprocessors.register(HtmlBlockPreprocessor(md), 'html_block', 20)
    preprocessors.register(ReferencePreprocessor(md), 'reference', 10)
    return preprocessors

NormalizeWhitespace > HtmlBlockPreprocessor > ReferencePreprocessor Le prétraitement est enregistré dans l'ordre de priorité.

Comme le nom le suggère NormalizeWhitespace: Normalisation des caractères vides et sauts de ligne (implémentation d'environ 10 lignes) HtmlBlockPreprocessor: Analyse des éléments html (250 lignes ...!) ReferencePreprocessor: Trouvez le lien exprimé au format Titre et enregistrez-le dans le dictionnaireMarkdown.references (30 lignes)

Sautons le contenu de HtmlBlockPreprocessor. Un processus de détection d'élément similaire devrait être en attente aux étapes 2 et 3 ...

(De côté) Comme ReferencePreprocessor, la scène où vous devez juger si ce sera le contenu de l'élément jusqu'à ce que la ligne suivante apparaisse souvent dans l'analyseur, et quand vous le faites vous-même (étude)

while lines:
    line_num += 1
    line = self.lines[line_num]
    <En traitement>
    if (Inclure jusqu'à la ligne suivante):
        line_num += 1
        line = self.lines[line_num]

Cependant, Reference Preprocessor utilise pop.

while lines:
    line = lines.pop(0)

Ouais, j'aurais dû faire ça ... transpirer

(Fin de côté)

Étape 2 Perspective sur l'arbre Erement

Ce processus est la partie suivante.

core.py



class Markdown:

    def convert(self, source):
        <Abréviation>
        # Parse the high-level elements.
        root = self.parser.parseDocument(self.lines).getroot()

Ici, «self.parser» est le «BlockParser» obtenu par la fonction suivante.

blockprocessors.py



def build_block_parser(md, **kwargs):
    """ Build the default block parser used by Markdown. """
    parser = BlockParser(md)
    parser.blockprocessors.register(EmptyBlockProcessor(parser), 'empty', 100)
    parser.blockprocessors.register(ListIndentProcessor(parser), 'indent', 90)
    parser.blockprocessors.register(CodeBlockProcessor(parser), 'code', 80)
    parser.blockprocessors.register(HashHeaderProcessor(parser), 'hashheader', 70)
    parser.blockprocessors.register(SetextHeaderProcessor(parser), 'setextheader', 60)
    parser.blockprocessors.register(HRProcessor(parser), 'hr', 50)
    parser.blockprocessors.register(OListProcessor(parser), 'olist', 40)
    parser.blockprocessors.register(UListProcessor(parser), 'ulist', 30)
    parser.blockprocessors.register(BlockQuoteProcessor(parser), 'quote', 20)
    parser.blockprocessors.register(ParagraphProcessor(parser), 'paragraph', 10)
    return parser

Les transformateurs sont également enregistrés ici avec leurs priorités. Cliquez ici pour BlockParser.praseDocument ().

blockparser.py



class BlockParser:

    def __init__(self, md):
        self.blockprocessors = util.Registry()
        self.state = State()
        self.md = md

    #Créer un ElementTree
    def parseDocument(self, lines):
        self.root = etree.Element(self.md.doc_tag)
        self.parseChunk(self.root, '\n'.join(lines))
        return etree.ElementTree(self.root)

    def parseChunk(self, parent, text):
        self.parseBlocks(parent, text.split('\n\n'))

    def parseBlocks(self, parent, blocks):
        while blocks:
            for processor in self.blockprocessors:
                if processor.test(parent, blocks[0]):
                    if processor.run(parent, blocks) is not False:
                        break

en bref,

core.py


root = self.parser.parseDocument(self.lines).getroot()

Dans la partie de, chaque "Block Processor" est fait pour traiter.

Par exemple, un processeur qui gère le format d'en-tête de hashtag "# header" est défini comme suit:

blockprocessors.py



class HashHeaderProcessor(BlockProcessor):
    """ Process Hash Headers. """

    RE = re.compile(r'(?:^|\n)(?P<level>#{1,6})(?P<header>(?:\\.|[^\\])*?)#*(?:\n|$)')

    def test(self, parent, block):
        return bool(self.RE.search(block))

    def run(self, parent, blocks):
        block = blocks.pop(0)
        m = self.RE.search(block)
        if m:
            ```d'ici```
            before = block[:m.start()]
            after = block[m.end():]
            if before:
                #Traitement récursif uniquement pour la partie avant
                self.parser.parseBlocks(parent, [before])
            h = etree.SubElement(parent, 'h%d' % len(m.group('level')))
            h.text = m.group('header').strip()
            if after:
          #Puis ajoutez au début des blocs à traiter après
                blocks.insert(0, after)
            ```C'est le noyau```
        else:
            logger.warn("We've got a problem header: %r" % block)

Regardons à nouveau le processeur pour les blocs de guillemets au format "> texte".

Ce à quoi vous devez penser dans le bloc de devis -Les blocs continus sur plusieurs lignes sont considérés comme un seul bloc. ・ Le contenu du bloc doit également être analysé C'est vrai.

blockprocessors.py



class BlockQuoteProcessor(BlockProcessor):

    RE = re.compile(r'(^|\n)[ ]{0,3}>[ ]?(.*)')

    def test(self, parent, block):
        return bool(self.RE.search(block))

    def run(self, parent, blocks):
        block = blocks.pop(0)
        m = self.RE.search(block)
        if m:
            before = block[:m.start()]
            #C'est la même chose que le processeur d'en-tête de hachage
            self.parser.parseBlocks(parent, [before])
            #Au début de chaque ligne">"Effacer
            block = '\n'.join(
                [self.clean(line) for line in block[m.start():].split('\n')]
            )
        ```Vérifiez si le bloc de citation a continué ou est-ce le début```
        sibling = self.lastChild(parent)
        if sibling is not None and sibling.tag == "blockquote":
            quote = sibling
        else:
            quote = etree.SubElement(parent, 'blockquote')
        self.parser.state.set('blockquote')
        ```Analysez le contenu du bloc cité. Les parents sont dans le bloc actuel (citation)```
        self.parser.parseChunk(quote, block)
        self.parser.state.reset()

Soit dit en passant, «frère» est un mot pour frères et sœurs.

J'ai examiné deux classes de processeur, mais où stockez-vous vos résultats d'analyse? etree.SubElement(parent, <tagname>) La partie est suspecte.

En premier lieu, etree est une instance de xml.etree.ElementTree dans la bibliothèque standard de python. Par ʻetree.SubElement (parent, ) , vous ajoutez un élément enfant à BlockParser (). Root (également une instance de ʻElementTree).

Au fur et à mesure que le processus progresse, les résultats sont enregistrés sous le nom BlockParser (). Root.

Étape 3 Processeur d'arborescence

Comme auparavant, cette fois, mordez le «processeur d'arbre».

treeprocessors.py



def build_treeprocessors(md, **kwargs):
    """ Build the default treeprocessors for Markdown. """
    treeprocessors = util.Registry()
    treeprocessors.register(InlineProcessor(md), 'inline', 20)
    treeprocessors.register(PrettifyTreeprocessor(md), 'prettify', 10)
    return treeprocessors

ʻInlineProcessor: Traitement des éléments en ligne PrettifyTreeprocessor`: Traitement des caractères de saut de ligne, etc.

À la fin

Ouais, c'est fini! ?? Mais avez-vous vu le modèle de conception approximatif de cette bibliothèque? Après cela, s'il y a une partie qui vous tient à cœur, il vaut mieux la voir par vous-même ...

Je suis un peu fatigué.

Merci d'avoir regardé jusqu'au bout ...!

Recommended Posts

Lire la source Python-Markdown: Comment créer un analyseur
Comment créer un sous-menu avec le plug-in [Blender]
Comment créer un package Conda
Comment lire l'ensemble de données SNLI
Comment créer un pont virtuel
Comment créer un Dockerfile (basique)
Comment créer un fichier de configuration
Comment créer un clone depuis Github
Comment créer un dossier git clone
Comment faire une commande pour lire le fichier de paramètres avec pyramide
Comment créer un enregistrement en collant une relation au modèle source d'héritage dans le modèle hérité par Django
Comment créer un référentiel à partir d'un média
Comment créer un wrapper qui préserve la signature de la fonction à envelopper
[Environnement de développement] Comment créer un ensemble de données proche de la base de données de production
Comment calculer la volatilité d'une marque
Comment lire un fichier CSV avec Python 2/3
Comment créer un objet fonction à partir d'une chaîne
Comment créer un fichier JSON en Python
J'ai lu "Comment créer un laboratoire de piratage"
[Note] Comment créer un environnement de développement Ruby
Comment créer une boîte de saisie Kivy 1 ligne
Procédure de création d'application multi-plateforme avec kivy
Comment créer une API Rest dans Django
Comment lire des fichiers dans différents répertoires
Créez une commande pour obtenir le journal de travail
[Note] Comment créer un environnement de développement Mac
Comment lire PyPI
Comment lire JSON
Comment créer un article à partir de la ligne de commande
Créer une fonction pour visualiser / évaluer le résultat du clustering
Comment écrire une interface graphique à l'aide de la commande maya
[Go] Comment créer une erreur personnalisée pour Sentry
Comment créer un référentiel local pour le système d'exploitation Linux
Comment créer un simple script serveur / client TCP
Comment lire le journal du solveur CBC (Pulp, python-mip)
Comment publier un ticket depuis l'API Shogun
[Python] Comment créer un histogramme bidimensionnel avec Matplotlib
Comment utiliser le générateur
Comment appeler une fonction
Comment pirater un terminal
Comment utiliser le décorateur
Comment augmenter l'axe
Comment démarrer la première projection
[Ubuntu] Comment supprimer tout le contenu du répertoire
Comment changer le fichier de configuration pour qu'il soit lu par Python
Comment utiliser la méthode __call__ dans la classe Python
Probablement le moyen le plus simple de créer un pdf avec Python 3
Comment créer un maillage de flux autour d'un cylindre avec SnappyHexMesh
[Python Kivy] Comment créer une simple fenêtre pop-up
Comment générer une requête à l'aide de l'opérateur IN dans Django
Comment obtenir la dernière (dernière) valeur d'une liste en Python
Une histoire sur la façon de traiter le problème CORS
Comment trouver le coefficient de mise à l'échelle d'une ondelette bipolaire
Road to the Wizard: Étape 8 (Interpréter comment devenir un hacker)
Je veux créer un Dockerfile pour le moment.
Comment connecter le contenu de la liste dans une chaîne de caractères
Comment créer un nouveau fichier lorsque le fichier spécifié n'existe pas - Ecrire si le fichier existe