Leerzeichen im Quelltext entfernen
Aus APF Wiki [de]
Inhaltsverzeichnis |
Einleitung
Dieses Tutorial soll zeigen, wie an Hand von dedizierten Steuer-Zeichen Leerzeichen und Leerzeilen aus dem Quelltext entfernt werden können. Hintergrund der Frage unter Leerzeichen im Quelltext ist, dass durch strukturiert definierte APF-Templates der Form
<@controller namespace="..." file="..." class="new_wiki_articles_controller" @> <h3>Neue Wiki-Artikel:</h3> <ul> <html:placeholder name="articles" /> </ul> <html:template name="article_de"> <li> <strong><template:placeholder name="name" /></strong> (Datum: <template:placeholder name="date" />, Revision: <template:placeholder name="revision" />) </li> </html:template>
bei der Generierung der Ausgabe Leerzeichen und Leerzeilen entstehen. Sofern die Webseite gzip komprimiert ausgeliefert wird (siehe Apache's mod_deflate), stellt das weiter kein Problem dar.
Sofern diese Möglichkeit nicht gegeben ist oder der Quelltext explizit gestaltet werden möchte, so kann ein spezieller APF-Output-Filter dafür sorgen, dass die nicht erwünschten Leerzeichen und Leerzeilen entfernt werden.
Umsetzung
Die Idee der Umsetzung stammt aus JSP-Templates, die im JAVA-Bereich für die Gestaltung von Views Einsatz finden. Hier ist es möglich, Scriptlet-Tags (<% bzw. %>) dazu einzusetzen, unerwünschte Zeichen zu entfernen.
Soll beispielsweise der Umbruch zwischen
</ul>und
<html:template name="article_de">entfernt werden, kann das per
</ul><% %><html:template name="article_de">
erreicht werden.
Da dieser Ansatz nur dann greift, wenn die aufgezeigten Scriptlet-Tags auch eingesetzt werden, ist der Entwickler aufgefordert, diese überall dort einzusetzen, wo Leezeichen und Leerzeilen entfernt werden sollen!
Um dieses Verhalten auch auf die Ausgabe des APF anzuwenden, kann ein eigener Output-Filter implementiert werden, der um diese Funktion erweitert ist. Die folgenden Code-Boxen zeigt wie:
Für APF <= 1.13
import('core::filter','GenericOutputFilter');
class ScriptletOutputFilter extends GenericOutputFilter {
public function filter($input){
// Bisherige Funktionalität ausführen
$input = parent::filter($input);
// Scriptlet Tags entfernen
$t = Singleton::getInstance('BenchmarkTimer');
$reportId = 'whitespace_killer';
$t->start($reportId);
$input = preg_replace('/<%([^%>]+)%>/ims','',$input);
$t->stop($reportId);
return $input;
}
}Wie der Methode filter() zu entnehmen ist, wird die Ausführung zusätzlich mit dem BenchmarkTimer gemessen. Sofern ein Report ausgegeben wird, erscheint eine zusätzliche Messung, die die Ausführungszeit der Filterung ausgibt. Ist dieses nicht gewünscht, müssen die jeweiligen Zeilen einfach auskommentiert werden.
Für APF >= 1.14
import('core::filter', 'FilterChain');
class ScriptletOutputFilter implements ChainedContentFilter {
public function filter(FilterChain &$chain, $input = null) {
// Scriptlet Tags entfernen
$t = Singleton::getInstance('BenchmarkTimer');
$reportId = 'whitespace_killer';
$t->start($reportId);
$input = preg_replace('/<%([^%>]+)%>/ims','',$input);
$t->stop($reportId);
return $chain->filter($input);
}
}
Anwendung
Um den Output-Filter anzuwenden, muss dieser in der Bootstrap-Datei in der Registry definiert werden. Hierzu ist folgender Code notwendig:
Für APF <= 1.13
Registry::register( 'apf::core::filter', 'OutputFilter', new FilterDefinition('my::filter::namespace','ScriptletOutputFilter') );
Für APF >= 1.14
import('my::filter::namespace','ScriptletOutputFilter'); OutputFilterChain::getInstance()->addFilter(new ScriptletOutputFilter());
Alternative Umsetzungen
Zwei Alternativen zu oben beschriebenen Verfahren, sind "radikale" Reguläre Ausdrücke. Diese Variante hat den Vorteil, dass keine extra <% %> tags gesetzt werden müssen, jedoch kann sie in Einzelfällen auch gewünschte Formatierungen zerstören.
Beide varianten funktionieren mit obigem codebeispiel, man muss nur die Zeile
$input = preg_replace('/<%([^%>]+)%>/ims','',$input);ersetzen.
Variante 1:
Ziel dieser Variante ist, alle unnötigen Leerzeilen zu entfernen, aber (in den meisten Fällen) den HTML-code trotzdem lesbar zu halten, indem gewollte Zeilenumbrüche nicht entfernt werden. Diese Variante ist im Vergleich zur Variante 2 weniger Fehleranfällig im Bezug auf zerstörte Formatierungen.
$input = preg_replace('/((\r)?\n(\s)*){2,}/','',$input);
Es werden nur 2 aufeinanderfolgende Leerzeilen entfernt, wobei diese Leerzeichen (z.b. aufgrund der Einrückung) enthalten dürfen.
Variante 2:
Diese Variante ist sehr radikal. Sie entfernt alle Leerzeilen, Tabulatoren, und auch Leerzeichen wenn mindestens 3 hintereinander vorkommen (um html-tags nicht zu zerstören). Übrig bleibt meist ein einzeiliger HTML-code. Diese Variante sollte mit Bedacht benutzt werden und es sollte gut getestet werden ob womöglich irgendwo zu viel entfernt wird.
$input = preg_replace('/\r|\n|\t|\s{3,}/','',$input);
Variante 3:
Hier noch eine Möglichkeit um den Quelltext auf eine Zeile zu reduzieren:
$input = preg_replace( array('/>\s+/', '/\s+</', '/[\r\n]+/'), array('>', '<', ' '), $string );
Variante 4:
Eine etwas umfassendere Variante, welche jedoch berücksichtigt, dass textarea- und pre-Inhalte nicht gefiltert werden dürfen (andernfalls werden z.B. Formulardaten verstümmelt, sofern sie nochmal angezeigt werden) ist Folgende: Zuerst werden die genannten Tags extrahiert, mit einem Platzhalter ersetzt, danach der Inhalt nach einem der obigen Filter gefiltert, und anschließend die Platzhalter wieder mit den richtigen Tags ersetzt:
<?php import('core::filter', 'FilterChain'); class ShrinkOutputFilter implements ChainedContentFilter { protected $Replacements = array(); public function filter(FilterChain &$chain, $input = null) { /* Save content from textareas and <pre> element from beeing destroyed */ $input = preg_replace_callback('/<textarea(.*)>(.*)<\/textarea>/Usi', array('ShrinkOutputFilter', 'callback'), $input); $input = preg_replace_callback('/<pre(.*)>(.*)<\/pre>/Usi', array('ShrinkOutputFilter', 'callback'), $input); $input = preg_replace( array('/\r|\n|\t/', '/\s{2,}/'), array('', ' '), $input ); /* Re-add <textarea> and <pre> content */ foreach($this->Replacements as $Md5 => $Html) { $input = str_replace('{{ShrinkOutputFilter::'.$Md5.'}}', $Html, $input); } return $chain->filter($input); } protected function callback($Hits) { $Md5 = md5($Hits[0] . mt_rand(0, 999999)); $this->Replacements[$Md5] = $Hits[0]; return '{{ShrinkOutputFilter::'.$Md5.'}}'; } } ?>