Lesen Sie die Python-Markdown-Quelle: So erstellen Sie einen Parser

Zweck

Ich habe mich entschlossen zu lernen, wie die Markdown-Parser-Bibliothek implementiert ist. (Abrupt) Es wird gesagt, dass das Design (Designmuster) von Javas Dom-Parser wunderbar ist, aber zuerst habe ich nach einer Python-Bibliothek gesucht, an die ich gewöhnt bin.

Das ist übrigens Java. https://www.tutorialspoint.com/java_xml/java_dom_parse_document.htm

Dieses Mal werde ich die Quelle der folgenden Bibliothek lesen.

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

Es scheint, dass Sie in Markdown-> HTML konvertieren können. Natürlich analysieren wir Markdown im Inneren, also lasst uns sehen, was für ein Design es ist!

Wenn Sie etwas falsch mit Ihrem Verständnis finden, weisen Sie bitte darauf hin ...!

Hinweis

Es scheint, dass die Kernfunktionen unter "Python-Markdown / markdown / markdown /" zusammengefasst sind. Da dies in Zukunft problematisch sein wird, werden die Dateien in diesem Verzeichnis grundsätzlich als "sample.py" abgekürzt.

Darüber hinaus ist der unten angegebene Quellcode im Grunde genommen nur ein Auszug der erforderlichen Teile (+ auskommentieren und ein Memo schreiben).

Der Hauptteil wird zuerst verwöhnt

Unter dem Strich wurde jeder Prozessor, der ein bestimmtes Element erkennt, Block für Block aktiviert. Das Ziel, auf das reagiert werden soll, ist jeder Block des Originaltextes, geteilt durch "\ n \ n". Dies ist zum Beispiel

<b>Unvollständiges Tag, aber fett

Nicht fett gedruckt</b>

Wenn eine Leerzeile eingefügt wird (= "\ n \ n" erscheint), wird der effektive Bereich des Elements abgeschnitten.

In diesem Beispiel zuerst

["<b>Unvollständiges Tag, aber fett", "Nicht fett gedruckt</b>"」

Für jeden Block wie zuerst ist das " -Tag" unvollständig, aber für das Fett "" handeln die Prozessoren, die jedes Element erkennen, in der richtigen Reihenfolge und fahren mit dem nächsten Block fort ... Ich denke, dass es der Fluss sein wird.

Prozessablauf

Das Herzstück der Benutzeroberfläche in dieser Bibliothek ist die folgende "Markdown" -Klasse und ihre "convert" -Methode.

core.py


class Markdown:

    #Konvertieren Sie hier in HTML
    def convert(self, source):
        # source :Markdown-Text

Es gibt einen Kommentar zur "convert" -Methode, und es sieht so aus, wenn es ins Japanische übersetzt wird.

  1. Präprozessoren konvertieren Text Analysieren Sie das übergeordnete Strukturelement des in 2, 1 vorverarbeiteten Textes in den Elementbaum
  1. Die Baumprozessoren verarbeiten den Elementbaum. Inline-Muster finden beispielsweise Inline-Elemente
  2. Lassen Sie einige Postprozessoren an dem text-serialisierten ElementTree arbeiten.
  3. Schreiben Sie das Ergebnis in eine Zeichenfolge

Der Originaltext ist möglicherweise leichter zu lesen w

Schritt 1 Präprozessoren

core.py



class Markdown:

    def convert(self, source):
        <Abkürzung>
        self.lines = source.split("\n")
        for prep in self.preprocessors:
            self.lines = prep.run(self.lines)

Teilen Sie zuerst in jede Zeile und lassen Sie die Vorverarbeitung beißen. Die "Präprozessoren" werden unten erhalten.

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 Die Vorverarbeitung wird in der Reihenfolge ihrer Priorität registriert.

Wie der Name schon sagt NormalizeWhitespace: Normalisierung von Leerzeichen und Zeilenvorschubzeichen (Implementierung von ca. 10 Zeilen) HtmlBlockPreprocessor: Analyse von HTML-Elementen (250 Zeilen ...!) ReferencePreprocessor: Suchen Sie den Link im Format Title und registrieren Sie ihn im WörterbuchMarkdown.references (30 Zeilen).

Lassen Sie uns den Inhalt von HtmlBlockPreprocessor überspringen. Ein ähnlicher Elementerkennungsprozess sollte in den Schritten 2 und 3 warten ...

(Beiseite) Wie ReferencePreprocessor die Szene, in der Sie beurteilen müssen, ob es sich um den Inhalt des Elements handelt, bis die nächste Zeile häufig im Parser angezeigt wird, und wann Sie Ihre eigene erstellen (Studie)

while lines:
    line_num += 1
    line = self.lines[line_num]
    <wird bearbeitet>
    if (Bis zur nächsten Zeile einschließen):
        line_num += 1
        line = self.lines[line_num]

Reference Preprocessor verwendet jedoch pop.

while lines:
    line = lines.pop(0)

Ja, das hätte ich tun sollen ... Schweiß

(Ende beiseite)

Schritt 2 Perspektive auf den Erementbaum

Dieser Prozess ist der folgende Teil.

core.py



class Markdown:

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

Hier ist "self.parser" der "BlockParser", der durch die folgende Funktion erhalten wird.

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

Hier werden auch Prozessoren mit ihren Prioritäten registriert. Klicken Sie hier für "BlockParser.praseDocument ()".

blockparser.py



class BlockParser:

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

    #Erstellen Sie einen Elementbaum
    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

Zusamenfassend,

core.py


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

In dem Teil von wird jeder "Blockprozessor" zur Verarbeitung gebracht.

Ein Prozessor, der das Format "# header" des Hashtag-Headers verarbeitet, ist beispielsweise wie folgt definiert:

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:
            ```von hier```
            before = block[:m.start()]
            after = block[m.end():]
            if before:
                #Rekursive Verarbeitung nur für den vorherigen Teil
                self.parser.parseBlocks(parent, [before])
            h = etree.SubElement(parent, 'h%d' % len(m.group('level')))
            h.text = m.group('header').strip()
            if after:
          #Fügen Sie dann am Anfang der Blöcke hinzu, die danach verarbeitet werden sollen
                blocks.insert(0, after)
            ```Das ist der Kern```
        else:
            logger.warn("We've got a problem header: %r" % block)

Schauen wir uns den Prozessor für Anführungszeichenblöcke im Format "> Text" noch einmal an.

Woran Sie im Anführungszeichen denken müssen -Blöcke, die auf mehreren Zeilen durchgehend sind, werden als ein Block betrachtet. ・ Der Inhalt des Blocks muss ebenfalls analysiert werden Korrekt.

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()]
            #Dies entspricht dem Hash-Header-Prozessor
            self.parser.parseBlocks(parent, [before])
            #Am Anfang jeder Zeile">"Löschen
            block = '\n'.join(
                [self.clean(line) for line in block[m.start():].split('\n')]
            )
        ```Überlegen Sie, ob der Zitierblock fortgesetzt wurde oder ob dies der Anfang ist```
        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')
        ```Analysieren Sie den Inhalt des zitierten Blocks. Eltern sind im aktuellen Block (Zitat)```
        self.parser.parseChunk(quote, block)
        self.parser.state.reset()

"Geschwister" ist übrigens ein Wort für Brüder und Schwestern.

Ich habe mir zwei Prozessorklassen angesehen, aber wo speichern Sie Ihre Analyseergebnisse? etree.SubElement(parent, <tagname>) Der Teil ist verdächtig.

Erstens ist etree eine Instanz von "xml.etree.ElementTree" in der Standardbibliothek von Python. "etree.SubElement (parent, )" bedeutet, dass Sie "BlockParser (). Root" (auch eine "ElementTree" -Instanz) ein untergeordnetes Element hinzufügen.

Im Verlauf des Prozesses werden die Ergebnisse als "BlockParser (). Root" gespeichert.

Schritt 3 Baumprozessor

Nach wie vor beißt diesmal den "Baumprozessor".

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: Verarbeitung für Inline-Elemente PrettifyTreeprocessor: Verarbeitung von Zeilenvorschubzeichen usw.

Am Ende

Ja, es ist vorbei! ?? Aber haben Sie das grobe Entwurfsmuster dieser Bibliothek gesehen? Wenn es danach einen Teil gibt, den Sie interessieren, ist es besser, ihn selbst zu sehen ...

Ich bin etwas müde.

Danke fürs Zuschauen bis zum Ende ...!

Recommended Posts

Lesen Sie die Python-Markdown-Quelle: So erstellen Sie einen Parser
So erstellen Sie ein Untermenü mit dem Plug-In [Blender]
So erstellen Sie ein Conda-Paket
Lesen des SNLI-Datensatzes
So erstellen Sie eine virtuelle Brücke
Wie erstelle ich eine Docker-Datei?
So erstellen Sie eine Konfigurationsdatei
So erstellen Sie einen Klon aus Github
So erstellen Sie einen Git-Klonordner
So erstellen Sie einen Befehl zum Lesen der Einstellungsdatei mit Pyramide
So erstellen Sie einen Datensatz, indem Sie eine Beziehung zum geerbten Modell in das von Django geerbte Modell einfügen
So erstellen Sie ein Repository aus Medien
So erstellen Sie einen Wrapper, der die Signatur der zu umschließenden Funktion beibehält
[Entwicklungsumgebung] So erstellen Sie einen Datensatz in der Nähe der Produktionsdatenbank
So berechnen Sie die Volatilität einer Marke
Lesen einer CSV-Datei mit Python 2/3
So erstellen Sie ein Funktionsobjekt aus einer Zeichenfolge
So erstellen Sie eine JSON-Datei in Python
Ich las "Wie man ein Hacking Lab macht"
[Hinweis] So erstellen Sie eine Ruby-Entwicklungsumgebung
So erstellen Sie ein 1-zeiliges Kivy-Eingabefeld
Verfahren zur Erstellung plattformübergreifender Apps mit kivy
So erstellen Sie eine Rest-API in Django
So lesen Sie Dateien in verschiedenen Verzeichnissen
Erstellen Sie einen Befehl, um das Arbeitsprotokoll abzurufen
[Hinweis] So erstellen Sie eine Mac-Entwicklungsumgebung
Wie man PyPI liest
Wie man JSON liest
So erstellen Sie einen Artikel über die Befehlszeile
Erstellen Sie eine Funktion zur Visualisierung / Auswertung des Clustering-Ergebnisses
So schreiben Sie eine GUI mit dem Befehl maya
[Go] So erstellen Sie einen benutzerdefinierten Fehler für Sentry
So erstellen Sie ein lokales Repository für Linux
So erstellen Sie ein einfaches TCP-Server / Client-Skript
Lesen des CBC-Löserprotokolls (Pulp, Python-Mip)
So veröffentlichen Sie ein Ticket über die Shogun-API
[Python] So erstellen Sie mit Matplotlib ein zweidimensionales Histogramm
Verwendung des Generators
So rufen Sie eine Funktion auf
Wie man ein Terminal hackt
Wie benutzt man den Dekorateur?
So erhöhen Sie die Achse
So starten Sie die erste Projektion
[Ubuntu] So löschen Sie den gesamten Inhalt des Verzeichnisses
So wechseln Sie die Konfigurationsdatei, die von Python gelesen werden soll
Verwendung der Methode __call__ in der Python-Klasse
Wahrscheinlich der einfachste Weg, um mit Python 3 ein PDF zu erstellen
So erstellen Sie mit snappyHexMesh ein Flussnetz um einen Zylinder
[Python Kivy] So erstellen Sie ein einfaches Popup-Fenster
So generieren Sie eine Abfrage mit dem IN-Operator in Django
So erhalten Sie den letzten (letzten) Wert in einer Liste in Python
Eine Geschichte über den Umgang mit dem CORS-Problem
So ermitteln Sie den Skalierungskoeffizienten eines bipolaren Wavelets
Weg zum Assistenten: Schritt 8 (Interpretieren, wie man ein Hacker wird)
Ich möchte vorerst eine Docker-Datei erstellen.
So verbinden Sie den Inhalt der Liste mit einer Zeichenfolge
So erstellen Sie eine neue Datei, wenn die angegebene Datei nicht vorhanden ist - schreiben Sie, wenn die Datei vorhanden ist