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 ...!
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).
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.
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.
- Präprozessoren konvertieren Text Analysieren Sie das übergeordnete Strukturelement des in 2, 1 vorverarbeiteten Textes in den Elementbaum
Der Originaltext ist möglicherweise leichter zu lesen w
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)
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,
Im Verlauf des Prozesses werden die Ergebnisse als "BlockParser (). Root" gespeichert.
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.
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