HTML-Generator. Markup-Parser, Templates?

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
arghargh
User
Beiträge: 81
Registriert: Donnerstag 4. September 2008, 22:26

Ich möchte ein Programm schreiben, das Daten aus einer Verzeichnisstruktur sammelt und daraus statisches HTML baut.

Markup wie Textile oder Markdown ist ein Muß, allerdings genügt es meinen Ansprüchen nicht. D.h. ich brauche einen Parser, der leicht zu erweitern ist.
Das Ganze kann auch reines Python sein, da es nicht auf eine hohe Geschwindigkeit ankommt.

Ich stelle mir das so vor, dass ich eine Reihe von regulären Ausdrücken aufstelle, die regulär, oder mit dem Rückgabewert eines Handlers ersetzt werden.
Wahrscheinlich gibt es da noch bessere Ansätze, da habe ich keine Ahnung von.

Der zweite Punkt ist das Zusammenbauen vom HTML. Die Grundstruktur ist immer weitgehend gleich, aber ein paar Tags und vorallem der eigentliche Inhalt ändert sich natürlich.
Lohnt es sich, für eine einfache, statische Seite überhaupt, ein Templatesystem zu verwenden? Oder bin ich besser beraten, wenn ich einfach einige Strings definiere, und dann einfach zusammenbaue?
Es wäre vielleicht ganz praktisch, das Template als HTML direkt im Browser angucken zu können.

Wie immer freue ich mich über Anhaltspunkte, auf deren Basis ich meine Suche fortsetzen kann. Suchmaschinen haben keine Erfahrung! :-)
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

arghargh hat geschrieben:Markup wie Textile oder Markdown ist ein Muß, allerdings genügt es meinen Ansprüchen nicht. D.h. ich brauche einen Parser, der leicht zu erweitern ist.
Du kannst ja die Textile oder Markdown-Parser mal ansehen und gucken ob man da was erweitern kann. Bei den docutils (reStructuredText) geht das - siehe Sphinx.
arghargh hat geschrieben:Lohnt es sich, für eine einfache, statische Seite überhaupt, ein Templatesystem zu verwenden?
Ja. Templateengines lohnen sich fast immer.
arghargh hat geschrieben:Es wäre vielleicht ganz praktisch, das Template als HTML direkt im Browser angucken zu können.
Du suchst also etwas XML-basiertes? Dann schau dir mal Genshi an.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Y0Gi
User
Beiträge: 1454
Registriert: Freitag 22. September 2006, 23:05
Wohnort: ja

python-markdown lässt sich passabel erweitern. markdown2 ist zwar offenbar viel schneller und sauberer - Möglichkeiten der Erweiterbarkeit, die über reine Regex-basierte Erweiterung hinaus gehen (was für mich vollkommen unzureichend ist), habe ich leider nicht gefunden.
arghargh
User
Beiträge: 81
Registriert: Donnerstag 4. September 2008, 22:26

Danke schonmal.

pyTextile gibt es wohl hier:
http://code.google.com/p/pytextile/downloads/list

textile.py hat etwa 2900 Zeilen

Tendenziell finde ich Textile besser als Markdown. Ich brauche aber sowieso nicht alle Möglichkeiten. Letztenendes werden solche Parser wohl hauptsächlich auf RegExp aufgebaut sein!?
Dann kommt es für mich auch in Frage, etwas eigenes zu machen. Nur wie? ;-)

(Rubys regular expressions sind irgendwie besser zu bedienen, oder?)
Zuletzt geändert von arghargh am Sonntag 28. September 2008, 13:27, insgesamt 1-mal geändert.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

arghargh hat geschrieben:(Rubys regular expressions sind irgendwie besser zu bedienen, oder?)
Inwiefern? Besser im Sinne von "können weniger"?

Was es auch nocht gibt ist Wiki-Coral, das könntest du dir auch überlegen. Oder eben reST.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
arghargh
User
Beiträge: 81
Registriert: Donnerstag 4. September 2008, 22:26

Insofern, als dass sie als Objekt verwendet werden können und nicht erst umständlich compiled werden müssen.
Ich habe weder von Ruby noch von Python Ahnung, aber tendenziell finde ich die Python Syntax angenehmer.

Hier mal ein Vergleich:

Code: Alles auswählen

  r.gsub!(/((http|https|ftp)\S+)/i, '"\1":\1')

Code: Alles auswählen

url = re.compile('((http|https|ftp)\S+)',re.IGNORECASE)
text = url.sub(r'"\1":\1', text)
Naja, liegt wohl eher an der Funktion, nicht am Ausdruck. Es interessiert mich allerdings, ob man das re.IGNORECASE noch anders unterbringen kann. In der Doku war das kurz erwähnt, ich habe es aber nicht verstanden.

--

Jetzt habe ich gerade genshi ausprobiert, finde mich aber leider in der Dokumentation nicht zurecht.

Code: Alles auswählen

loader = TemplateLoader(['.'])
tmpl = loader.load('template.html')
outf.write(tmpl.generate(content=html).render('html', doctype='html'))
So werden bestehende Tags aus dem string content in unicode-Zeichen umgewandelt:
Das würde ich gerne abstellen.
arghargh
User
Beiträge: 81
Registriert: Donnerstag 4. September 2008, 22:26

Ich glaube meine Frage ist beantwortet:
http://genshi.edgewall.org/wiki/GenshiF ... lateoutput

Code: Alles auswählen

tmpl.generate(content=genshi.Markup(html)).render('xhtml', doctype='xhtml')
Y0Gi
User
Beiträge: 1454
Registriert: Freitag 22. September 2006, 23:05
Wohnort: ja

arghargh hat geschrieben:Letztenendes werden solche Parser wohl hauptsächlich auf RegExp aufgebaut sein!?
Solche Parser in der Regel ja, aber ich bin auch kein Parser- und Lexer-Guru.

Meine Kritik an markdown2 bezieht sich darauf, dass man als Ersetzung nur einen String mit \1-Platzhaltern angeben kann, jedoch keine Callable, in der man erweiterte Textersetzung z. B. auch mit Datenbank-Abfragen vornehmen kann.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

arghargh hat geschrieben:Naja, liegt wohl eher an der Funktion, nicht am Ausdruck.
Du kannst auch ``re.sub`` nehmen, wo du das Pattern als String angibst und es nicht explizit kompilieren musst. Kompilieren ist dann sinnvoll, wenn man den regulären Ausdruck mehrfach matchen will, dann muss nicht immer neu kompiliert werden.
arghargh hat geschrieben:Es interessiert mich allerdings, ob man das re.IGNORECASE noch anders unterbringen kann. In der Doku war das kurz erwähnt, ich habe es aber nicht verstanden.
Geht so:

Code: Alles auswählen

re.findall(r'abc(?i)', 'ABC')
arghargh hat geschrieben:So werden bestehende Tags aus dem string content in unicode-Zeichen umgewandelt:
Das würde ich gerne abstellen.
Das nennt sich Autoescaping und ist generell eine gute Idee (sowohl aus der Sicherheitssicht als auch der Standardkonformitäts-Sicht). Wenn man schon fertig gerendertes HTML an Genshi ausgibt, hat man idR. etwas falsch gemacht, so garantiert Genshi wohlgeformtes HTML, aber wenn man Autoescaping abschaltet, geht das nicht mehr.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
arghargh
User
Beiträge: 81
Registriert: Donnerstag 4. September 2008, 22:26

Wenn man schon fertig gerendertes HTML an Genshi ausgibt, hat man idR. etwas falsch gemacht,
Ich vermute, man sollte also entsprechende streams an genshi übergeben.
Wie stellt man das an? Material kommt von einem Markup-Parser wie z.B. pytextile. Ausserdem möchte ich von pygment erzeugtes HTML einbauen.
Schön wär auch noch, wenn mathematische Formeln in irgendeiner Form umgesetzt werden könnten.

Es wird immer komplizierter, dabei ist es doch so einfach! ;-)
markdown2 ... als Ersetzung ... keine Callable
Das ist natürlich schlecht.

Mal ein naiver Gedanke:
Kann man nicht eine Liste/Dictionary o.Ä. mit <regexstring>,<funktion> einfach abarbeiten und die matches an die funktion übergeben?

Oder lässt sich eine Syntax wie Textile nicht so einfach verarbeiten, wie ich mir das denke?
arghargh
User
Beiträge: 81
Registriert: Donnerstag 4. September 2008, 22:26

Hier ist ein ausführlicher englischer Artikel, "Wiki Markup Parser in Python":
http://wiki.sheep.art.pl/Wiki%20Markup% ... n%20Python
lunar

Leonidas hat geschrieben:Wenn man schon fertig gerendertes HTML an Genshi ausgibt, hat man idR. etwas falsch gemacht, so garantiert Genshi wohlgeformtes HTML, aber wenn man Autoescaping abschaltet, geht das nicht mehr.
Darüber lässt sich jetzt trefflich streiten. Fertiges HTML an die Template-Engine zu übergeben, ist imho durchaus üblich. Ich denke da an Formgeneratoren wie wtforms oder an Markup, dass erst durch einen anderen Generator fließt, wie z.B. bei Sphinx oder Inyoka.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

lunar hat geschrieben:
Leonidas hat geschrieben:Wenn man schon fertig gerendertes HTML an Genshi ausgibt, hat man idR. etwas falsch gemacht, so garantiert Genshi wohlgeformtes HTML, aber wenn man Autoescaping abschaltet, geht das nicht mehr.
Darüber lässt sich jetzt trefflich streiten. Fertiges HTML an die Template-Engine zu übergeben, ist imho durchaus üblich. Ich denke da an Formgeneratoren wie wtforms oder an Markup, dass erst durch einen anderen Generator fließt, wie z.B. bei Sphinx oder Inyoka.
Ja, das stimmt - irgendwie scheint mir das aber immer noch nicht optimal zu sein. Ich habe das auch gemacht, da die Django Templates so wenig konnten also musste ich dann Tags schreiben, die HTML generiert haben. Aber so ganz optimal ist das nicht. Besser wäre es statt einen String mit HTML zu übergeben, der Templatesprache das irgendwie in struktuerierter Form zu übergeben. Also effektiv als Sub-Template, dass in den Renderingprozess des gesamten Templates aufgenommen werden kann und dadurch auch validiert werden kann. Naja :?
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
lunar

Naja, HTML ist standardisiert, die Doctrees und ASTs verbreiteter Template-Engines und Markupparser eher nicht. Und die Vielzahl der Parser und Template-Engines macht die Implementierung dieser Idee ein bisschen schwierig ;)
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

arghargh hat geschrieben:Insofern, als dass sie als Objekt verwendet werden können und nicht erst umständlich compiled werden müssen.
Das stimmt nicht. Der Vergleich wäre eher:

Code: Alles auswählen

text = text.gsub(/(https?\S+)/, '"\1":\1')

Code: Alles auswählen

text = re.sub(r"(https?\S+)", r'"\1":\1"', text)
Ruby ist ein bisschen kürzer und IMHO eleganter, da `gsub` eine Methode von String ist und nicht eine globale Funktion eines Moduls, aber kompilieren muss man nicht, wenn man die zusätzliche Performance nicht will.

Ansonsten hier ein Beispiel, wie man sich selbst einen Übersetzer für eigenes Markup bauen kann. Sagen wir, ich will Absätze mit Leerzeilen trennen. Ich will Aufzählungen mit "* " einleiten können und Programmcode mit 4 Leerzeichen. < und & sollen automatisch in < und & verwandelt werden.

Code: Alles auswählen

import re

def format(text):
    text = "\n".join(format_line(line) for line in text.splitlines())
    return re.sub(r"</(\w+)>\n<\1>", "\n", text)

def format_line(line):
    if line.startswith("* "): return "<ul><li>%s</li></ul>" % format_inline(line[2:])
    if line.startswith("    "): return "<pre>%s</pre>" % escape(line[4:])
    if line.strip(): return "<p>%s</p>" % format_inline(line)
    return line

def format_inline(line):
    return escape(line)

def escape(line):
    return line.replace("&", "&").replace("<", "<")
Trickreich ist einzig `format`, das zunächst jede Zeile für sich betrachtet und dann Zeilen mit gleichem Format verschmilzt. Das reicht nicht für alles, aber ist deutlich einfacher als ein echter Parser. Weitere Zeilenformate wie etwa "== " für eine Überschrift lassen sich jetzt leicht hinzufügen. Was allerdings nicht so einfach geht, ist das Verhalten von Markdown zu realisieren, bei dem man Überschriften unterstreicht.

Möchte man z.B. Code innerhalb keiner Zeile mit ` ` markieren oder etwas mit * * hervorheben, lässt sich das wie folgt realisieren. Ich muss hier aufpassen, das ich alles in ` ` nicht mehr als Markup interpretiere.

Code: Alles auswählen

def format_inline(line):
    def repl(m):
        if m.group(2):
            return "<code>%s</code>" % m.group(2).strip()
        return re.sub(r'\*(.*?)\*', r'<strong>\1</strong>', m.group(3))
    return re.sub(r"(`+)(.*?)\1|([^`]*)", repl, escape(line))
An dieser Stelle könnte man dann auch noch Dinge machen, wie " in "echte" Anführungszeichen umzuwandeln, aus "--" und "..." entsprechende Satzzeichen zu machen oder weitere Auszeichnungen zu interpretieren.

Stefan
arghargh
User
Beiträge: 81
Registriert: Donnerstag 4. September 2008, 22:26

Danke für deinen Beitrag, ich kann hoffentlich am Wochenende wieder mit der Schlange spielen.
Y0Gi
User
Beiträge: 1454
Registriert: Freitag 22. September 2006, 23:05
Wohnort: ja

Na, da wird Mutti aber nicht begeistert sein :lol:
arghargh
User
Beiträge: 81
Registriert: Donnerstag 4. September 2008, 22:26

Ich habe gerade die "Schalter" gefunden. Gehören an den Anfang!
(?...)
This is an extension notation (a "?" following a "(" is not meaningful otherwise). The first character after the "?" determines what the meaning and further syntax of the construct is. Extensions usually do not create a new group; (?P<name>...) is the only exception to this rule. Following are the currently supported extensions.

(?iLmsux)
(One or more letters from the set "i", "L", "m", "s", "u", "x".) The group matches the empty string; the letters set the corresponding flags (re.I, re.L, re.M, re.S, re.U, re.X) for the entire regular expression. This is useful if you wish to include the flags as part of the regular expression, instead of passing a flag argument to the compile() function.

Note that the (?x) flag changes how the expression is parsed. It should be used first in the expression string, or after one or more whitespace characters. If there are non-whitespace characters before the flag, the results are undefined.
Und hier die Flags
I
IGNORECASE
Perform case-insensitive matching; expressions like [A-Z] will match lowercase letters, too. This is not affected by the current locale.

L
LOCALE
Make \w, \W, \b, \B, \s and \S dependent on the current locale.

M
MULTILINE
When specified, the pattern character "^" matches at the beginning of the string and at the beginning of each line (immediately following each newline); and the pattern character "$" matches at the end of the string and at the end of each line (immediately preceding each newline). By default, "^" matches only at the beginning of the string, and "$" only at the end of the string and immediately before the newline (if any) at the end of the string.

S
DOTALL
Make the "." special character match any character at all, including a newline; without this flag, "." will match anything except a newline.

U
UNICODE
Make \w, \W, \b, \B, \d, \D, \s and \S dependent on the Unicode character properties database. New in version 2.0.

X
VERBOSE
This flag allows you to write regular expressions that look nicer. Whitespace within the pattern is ignored, except when in a character class or preceded by an unescaped backslash, and, when a line contains a "#" neither in a character class or preceded by an unescaped backslash, all characters from the leftmost such "#" through the end of the line are ignored.
arghargh
User
Beiträge: 81
Registriert: Donnerstag 4. September 2008, 22:26

Also der Trick mit dem zeilenweisen Ersetzen und darauffolgendem Verschmelzen ist ganz gut, aber doch etwas eingeschränkt.

Problematisch sind vor allem vorhandene Tags / verschachtelte Strukturen. Die lassen sich schlecht ignorieren. Es muss wohl auch eine Art Priorität geben, damit z.B. Listen nicht zusätzlich auch ein <br/> angehängt bekommen. Es muss auch zwischen Zeilenumbruch und Absatz entscheiden werden.

Reguläre Ausdrücke sind gut geeignet um einzelne Elemente zu finden und zu ersetzen (z.B. Links). Da kann ich mir was zusammenschustern ;-)

Aber für den Rest muss ich wohl noch tiefer in die Materie einsteigen. Andererseits brauche ich eigentlich gar nicht 'viel' mehr, als das, was Stefans Code kann.

Es fehlen noch Zeilenumbrüche, die Möglichkeit vorhandene Tags zu berücksichtigen/ignorieren (da wird's wohl schwieriger, evtl. mit XML-Parser?) und Blöcke mit weiteren Angaben zu erstellen (spricht für XML).

Naja, ich bleibe dran.
Antworten