Knoten aus dem HTML kämmen

Mit dem Texteditor Notepad++ lassen sich Scripte in Python ausführen, die mittels einer Reihe von regulären Ausdrücken Dateien bereinigen. Das ist eine hervorragende Möglichkeit, exportierte HTML-Dateien auf Vordermann zu bringen.

Im Leben eines Nerds ereignen sich Dinge, von denen normale Leute in aller Regel verschont bleiben. Nerds passiert es zum Beispiel immer wieder, dass sie mit Code in Berührung kommen und diesen durch die Mangel drehen müssen.

Einen solchen Fall habe ich im Beitrag Der HTML-Hack für InDesign ausgeführt: Es geht darum, die aus der wunderbaren Adobe-Layoutsoftware exportierte HTML-Datei sinnvoll aufzubereiten. Mit einer Handvoll regulärer Ausdrücke werden Formatzuweisungen den Erfordernissen angepasst.

Grosse Textsammlungen bearbeiten

Hier geht es um ein zweites Beispiel: Mein Artikel-Archiv. Das benötige ich für Buchprojekte wie das Kummerbox kompakt-Buch und natürlich auch einfach, um an einer Stelle via die Volltextsuche rasant über alle Beiträge drüberschauen zu können. Beispielsweise um herauszufinden, ob ich schon über dieses oder jenes Thema geschrieben habe. (Man wird ja auch nicht jünger, nid wahr).

Kann alles, ausser einfach zu sein.

Dazu hole ich den publizierten Text als HTML aus der Mediendatenbank oder von der Website des Mediums und lege, falls möglich, auch ein PDF der Seite ab. Wer Ähnliches tut, der weiss: Fremder HTML-Code ist immer grauenvoll und nie so, wie man ihn gern haben will. Was in aller Regel nicht stört. Ausser, wenn man den Code gerne weiterverwenden möchte.

Und das möchte ich. Wenn ich eine Sammlung von Artikeln zu einem Buch kompiliere, dann hängt der Aufwand dafür massgeblich von der Qualität der HTML-Dateien ab. Wenn dort die Absatz- und Zeichenformate schon halbwegs richtig gesetzt sind, dann spare ich mir die Arbeit, diese von Hand manuell zu setzen. Ausserdem ist es so ein Fetisch von mir, dass Code rein und ästhetisch zu sein hat. Ich weiss auch nicht, woher das kommt.

HTML-Code von Hand aufzuräumen, ist nun auch keine reine Freude. Daher setze ich seit Längerem Notepad++ ein. Diesen kostenlosen Editor habe ich im Beitrag Das Textmonster besprochen. Für diesen gibt es die Erweiterung Python Script. Diese erlaubt es, wie der Name verrät, Dokumente über Python zu scripten.

Nach der Installation des Plug-ins erscheint es unter Erweiterungen > Python Script. Über Erweiterungen > Python Script > New Script richtet man ein neues Script ein, das unter C:/Users/Matthias/AppData/Roaming/Notepad++/plugins/config/PythonScript/scripts abgelegt wird. Die gespeicherten Scripts sind über Erweiterungen > Python Script > Scripts > [Scriptname] ausführbar. Um ein Script im Notepad++-Editor zu öffnen, klickt man es dort bei gedrückter Ctrl-Taste an. Das Ausführen der Scripts lässt sich vereinfachen, indem man über Erweiterungen > Python Script > Configuration die häufig benutzten Scripts ins Menü oder in die Symbolleiste legt.

Dazu wählt man im Dialog sein Script und fügt es als Menu Item oder Toolbar Item hinzu. Wenn man es der Symbolleiste hinzufügt, fällt einem auf, dass es dennoch nicht dort auftaucht – und man die Symbolleiste offenbar nicht anpassbar ist. Es geht aber wie folgt: Man klickt auf Erweiterungen > Plugin-Manager > Show Plugin-Manager, sucht in der Liste Customize Toolbar, installiert diese Erweiterung und Erweiterungen > Customize Toolbar > Customize, und fügt das Script der benutzerdefinierten Symbolleiste hinzu.

Das klingt nun alles etwas umständlich, und ist es auch. Notepad++ ist klar auf Leistung getrimmt und nicht auf Benutzerfreundlichkeit. Dennoch – da ich im Alltagseinsatz sehr viel Zeit spare, lohnt sich der ganze Heckmeck auf jeden Fall.

Nun geht es darum, das Script aufzusetzen und sich mit etwas Python-Code herumzuschlagen. Fürs Programmieren blendet man unbedingt via Erweiterungen > Python Script > Show Console die Konsole ein, in der man Fehlermeldungen sieht und sich Variablen oder Statusinfoausgeben lassen kann:

console.write("Dateiname: " + filename)

Selbst bin ich leider nicht der Python-Hirsch. Um Berge zu versetzen, müsste ich mich erst einmal in diese Sprache hineinknien. Um auf handgestrickte Weise das zu erledigen, was ich tun will, reicht es aber allemal. Ich tue im Wesentlichen folgende Dinge mit meinen Dateien:

Pfad anpassen

Wenn man HTML-Dateien speichert, dann werden Bilder, Scripts, CSS-Dateien und der ganze Kram in einen Unterordner verschoben, der den Namen der HTML-Datei, plus das Anhängsel «_Dateien» trägt. Also zum Beispiel «130624 Kummerbox-Dateien». Da in diesem Ordner immer die gleiche CSS-Datei steckt, will ich aber nicht pro Artikel einen separaten Unterordner haben – es reicht, einen Ordner für alle Artikel zu nutzen. Dieser heisst aus historischen Gründen «pix», und entsprechend sollen alle Referenzen auf den individuellen Ordner auf diesen «pix»-Ordner umgebogen werden. Das geht mit folgendem Code:

filename = notepad.getCurrentFilename()

if filename.endswith('.html'):
filename = filename[:-5]

if filename.endswith('.htm'):
filename = filename[:-4]

filename = os.path.basename(filename)
filename = urllib.quote(filename) + "-Dateien"

console.write("Dateiname: " + filename)

editor.replace(filename+ "/", "pix/")

Zeilenumbrüche bereinigen

In einem zweiten Schritt will ich die Zeilenumbrüche bereinigen. Zeilenbrüche an den falschen Stellen führen dazu, dass Suchen-Ersetzen-Durchgänge nicht korrekt ausgeführt warden, weil ein zu ersetzender Term wegen einem unnötigen Zeilenumbruch nicht erkannt wird. Ich verwende dazu folgenden Code – und es braucht mir nun niemand zu sagen, dass das Spaghetticode ist, und dass man dieses Problem viel eleganter lösen könnte. Das weiss ich, aber ich komme in diesem Fall gut ohne Eleganz zurecht.

# Zeilenumbrüche
editor.pyreplace(r"\r$\n", "|", 0, Editor.INCLUDELINEENDINGS)
editor.replace("|</DIV>", "</DIV>")
editor.replace("|</A>", "</A>")
editor.replace("|<TR", "<TR")
editor.replace("|<TD", "<TD")
editor.replace("|<TBODY>", "<TBODY>")
editor.replace("> |", ">|", 0)
editor.replace(">|", ">\r\n", 0)
editor.replace("|", "", 0)

Der Code verwandelt alle Zeilenumbrüche in das einfacher zu handhabende |-Zeichen (das in der Datei nicht vorkommen sollte, weil sonst ein Puff entsteht). Dann entferne ich es überall, wo es mir nicht passt. Im nächsten Schritt füge ich dort, wo ich Zeilenumbrüche haben will, wieder Zeilenumbrüche ein und entferne allfällige |, die irgendwo stehen geblieben sein könnten. Wie gesagt: Das ist die Cowboy-Methode. Aber sie erfüllt ihren Zweck.

Ich verwende dafür den normalen Suchen-Ersetzen-Befehl, der über editor.replace gescriptet wird. Der erste Parameter gibt den Suchen-Text, der zweite den Ersetzen-Text an. Simpel.

Nun lösche ich Zeugs raus, das ich nicht in meiner Datei drin haben will, beispielsweise der bescheuerte Generator-Tag von Microsoft:

editor.pyreplace(r"<META name=\"GENERATOR\" content=\"MSHTML \d\d\.\d\d.\d\d\d\d\.\d\d\d\d\d\"&gt;", "")

Dazu verwende ich den pyreplace-Befehl zur Ersetzung über einen regulären Ausdruck. Der erste Parameter enthält wiederum den Suchen-Text, der zweite den Text zum Ersetzen. Wenn im Suchen-Text ein regulärer Ausdruck enthalten ist, dann muss man ein r voranstellen, also r"Ausdruck". Wenn auch der Ersetzen-Text ein regulärer Text enthält, dann wäre auch dort ein r zu verwenden. Das ist im obigen Beispiel aber nicht der Fall.

Etwas mühsam bei der Arbeit mit den Scripten ist, dass man Anführungszeichen escapen muss. Die Parameter werden zwischen Anführungszeichen übergeben. Das heisst, dass die im Suchen- oder Ersetzen-Text enthaltenen Anführungszeichen als \" dargestellt werden müssen.

Geschützte Leerzeichen

Dann ersetze ich in bestimmten Fällen normale Leerzeichen durch geschützte Leerzeichen:

editor.pyreplace(r"(Word|Outlook|Excel|Powerpoint|Windows|Office|Access) (95|97|98|ME|2000|XP|2003|2007|2008|2011|2013)", r"\1&nbsp;\2")

Aber ich finde es nunmal schöner, wenn Dinge wie «Windows XP» als Einheit behandelt werden und das «XP» nicht auf eine neue Zeile rutscht. Und via Script macht die Ersetzung ja auch keine Arbeit.

Hier kommt wiederum ein regulärer Ausdruck zum Einsatz, der ganz viele Varianten in einem Rutsch abhandelt – von «Word 95» bis «Access 2013». Da der Ersetzen-Term zwei Elemente aus dem Suchen-Text übernimmt, muss ich diesen nun mit r einleiten, also r”\1&nbsp;\2”. Mit \1 nehme ich bezug auf den Inhalt der ersten Klammer, also Word oder Outlook oder Excel… etc. \2 bezieht sich auf die Versionsbezeichnung, also 95 oder 97 oder 98 oder ME, etc., und das Ganze wird dann durch ein geschütztes Leerzeichen (nonbraking space oder &nbsp;) verbunden. So einfach ist das.

Finale Bereinigungen

Dann führe ich diverse weitere Bereinigungen aus, d.h. ich vereinheitliche Formatierungen und entferne weitere Dinge, die mich stören. Ich entferne Anführungszeichen bei CSS-Klassen, entschärfe den div-Wahnsinn und passe Formatzuweisungen an:

editor.pyreplace(r"class=\"(.*?)\"", r"class=\1")
editor.pyreplace(r"<DIV class=(RE|HT|AU|SM)>(.*?)</DIV>", r"<p class=\1>\2</p>")
editor.pyreplace(r"<SPAN class=AU>(.*?)</SPAN>", r"\r\n<p class=AU>\1</p>\r\n", 0, Editor.INCLUDELINEENDINGS)

Notiz an mich

Das lästige Problem, dass Flatpress keine Backslashes anzeigt, lässt sich lösen, indem man den \ mit &#92; ausdrückt.

Kommentar verfassen