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 ...!
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).
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.
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.
- 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
Le texte original peut être plus facile à lire w
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é)
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
.
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.
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