WordPress-Funktionen nutzen, um HTML in Ihren Beiträgen zu reduzieren

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Es gibt eine Debatte darüber, ob HTML-Klassen zu Ihrem Inhalt gehören. Insbesondere Klassen, die sich streng auf die Darstellung dieser Inhalte beziehen. Manchmal ist die Verwendung dieser Klassen unvermeidlich. Ein Hervorhebungsabsatz, ein Zitatsplitter, ein Karussell mitten im Beitrag… Sie benötigen Klassen, um diese Dinge zu gestalten und ihnen Funktionalität zu verleihen.

Auch wenn Sie sie manchmal brauchen, gilt meiner Meinung nach: Je weniger Sie sie in den eigentlichen Beitragsinhalt schreiben, desto besser.

Warum HTML mit Klassen im Inhalt vermeiden?

Der Hauptgrund ist, dass diese HTML-Klassen zerbrechlich sind, da sie an Ihr aktuelles Theme gebunden sind. Bei der nächsten Neugestaltung besteht die Möglichkeit, dass sich diese Klassen ändern oder eine andere Struktur erfordern. Oder zumindest werden im Laufe der Zeit bestimmte Klassen vergessen, neue Klassen entstehen, doppelte Klassen auftreten und es wird unübersichtlich.

Die Änderung von HTML in Templates ist einfach, da ein Template für viele Seiten verantwortlich ist. Aber die Änderung von HTML im Inhalt ist schwierig. Es sind einzelne Dinge, manchmal Hunderte und Tausende, die möglicherweise manuell einzeln aktualisiert werden müssen.

Aber ich brauche diese HTML-Klassen!

Keine Sorge, WordPress ist flexibel genug, um uns die Generierung von HTML zu ermöglichen und es an der richtigen Stelle einzufügen.
Ihr Inhalt bleibt rein. Kein zerbrechliches HTML mehr. Wenn Sie rein bleiben, können Sie Ihren Beitragsinhalt einfach in Ihre Präsentationsbedürfnisse umwandeln und anpassen.

All diese Transformationen können mit Code erfolgen. Wenn Sie das Design das nächste Mal aktualisieren, aktualisieren Sie die Transformationsfunktion, um das richtige HTML zu generieren. Genau wie bei Templates nehmen Sie die Aktualisierungen an einer Stelle vor und sie wirken sich auf den gesamten Inhalt aus.

Kein manuelles Aktualisieren von Beiträgen mehr.

Strategien zur Anpassung von Inhalten

Unter allen von WordPress angebotenen Werkzeugen werden wir Folgendes verwenden:

  1. Shortcodes
  2. the_content-Filter

Ich werde kurz erklären, wie die beiden oben genannten funktionieren und einige reale Beispiele für Dinge geben, die Sie damit tun können.

Shortcodes

Shortcodes ermöglichen es Ihnen, ein Makro zu definieren, das sich zu etwas Ihrer Wahl erweitert. Sie sind im Grunde eine Art HTML-Tag, das Inhalt umschließt und Attribute akzeptiert. Zum Beispiel könnten Sie dies in den Beitragsinhalt einfügen

[my-shortcode foo="bar"]Hello, World![/my-shortcode]

Schreiben Sie dann Code, um ihn umwandeln zu lassen in

<aside data-foo="bar"><h3>Hello, World!</h3></aside>

Und haben Sie dann die Macht, diese Ausgabe jederzeit zu ändern.

WordPress hat umfangreiche Dokumentation für Shortcodes, aber ich werde ein einfaches Beispiel geben.

function css_tricks_example_shortcode( $attrs, $content = null ) {

    extract( shortcode_atts( array(
        'twitter' => ''
    ), $attrs ) );

    $twitterURL = 'https://twitter.com/' . $twitter;

    return <<<HTML
<p>This post has been written by $content. Follow him on Twitter</p>
HTML;

}
add_shortcode( 'author', 'css_tricks_example_shortcode' );

Dies ist ein künstliches Beispiel, aber wenn Sie den obigen Code in Ihre `functions.php`-Datei einfügen, können Sie einen Beitrag mit folgendem Inhalt erstellen

[author twitter="MisterJack"]Alessandro Vendruscolo[/author]

das dieses HTML rendert

<p>This post has been written by Alessandro Vendruscolo. Follow him on <a href="https://twitter.com/MisterJack">Twitter</a></p>

Filter

WordPress hat viele Filter verfügbar. Ein Filter ist eine Funktion, die die Möglichkeit hat, etwas zu transformieren, bevor es an die anfragende Entität zurückgegeben wird. Filter werden hauptsächlich von Plugins verwendet und sind das, was WordPress so anpassbar macht.

Der Filter, den wir verwenden werden, ist the_content, der eine Seite im Codex von WordPress hat.

Das Folgende ist ein grundlegendes Beispiel, wie man ihn benutzt.

function css_tricks_example_the_content( $content ) {
    global $post;
    $title = get_the_title( $post );
    $site = get_bloginfo( 'name' );
    $author = get_the_author( $post );
    return $content . '<p>The post ' . $title . ' on ' . $site . ' is by ' . $author . '.</p>';
}
add_filter( 'the_content', 'css_tricks_example_the_content' );

Dies fügt Text am Ende eines Beitrags hinzu, was für RSS-Scraper nützlich sein kann.

Mehr aus the_content herausholen

Die Dokumentation für den the_content-Filter liefert ähnliche Beispiele wie das obige, also machen wir etwas anderes. Wir werden uns mit einigen praktischen Beispielen aus der realen Welt befassen, nachdem wir die beteiligte Technik betrachtet haben.

Nehmen wir an, Sie schreiben bereits reine Beiträge und transformieren sie clientseitig mit JavaScript. Dies ist ein ziemlich häufiges Szenario. Nehmen wir an, Sie schreiben in Markdown und erstellen dreifach-Backtick-Codeblöcke. Diese werden in HTML umgewandelt wie…

<pre><code lang="js">
</code></pre>

Aber sagen wir, Ihre Syntax-Highlighting-Bibliothek benötigt die Codeblöcke so

<pre><code class="language-javascript">
</code></pre>

Sie machen vielleicht etwas wie…

$("code.js")
  .removeClass("js")
  .addClass("language-javascript");

// then do other languages

// then run syntax highlighter

Das funktioniert, erfordert aber bei jedem Seitenaufruf viel DOM-Aufwand. Es wäre besser, dieses HTML zu korrigieren, bevor es überhaupt zum Browser gelangt. Die Lösung dafür behandeln wir in den folgenden Beispielen.

Kombiniert mit einem HTML (technisch gesehen XML) -Parser wie libxml können wir DOM-Transformationen auf den Server verlagern und den Browser entlasten. Die Reduzierung der Menge an JavaScript, die im Frontend benötigt wird, ist definitiv ein gutes Ziel.

libxml hat Bindungen für PHP, die normalerweise in Standardinstallationen verfügbar sind. Sie müssen sicherstellen, dass Ihr Server PHP > 5.4 und libxml > 2.6 hat. Sie können dies überprüfen, indem Sie die Ausgabe von phpinfo() untersuchen oder die Kommandozeile verwenden

php -v
php -i | grep libxml

Wenn Ihr Server diese Anforderungen nicht erfüllt, sollten Sie Ihren Systemadministrator bitten, die erforderlichen Pakete zu aktualisieren.

Einen Beitrag parsen

Der von uns hinzugefügte Filter erhält das rohe HTML des Beitrags und gibt den transformierten Inhalt zurück.

Wir werden die Klasse DOMDocument verwenden, um das HTML zu laden und zu transformieren. Wir werden die Instanzmethode loadHTML verwenden, um den Beitrag zu parsen, und saveHTML, um das transformierte Dokument wieder in einen String zu serialisieren.

Es gibt einen kleinen Haken: Diese Klasse fügt automatisch die Definition <!doctype html> hinzu und umschließt den Inhalt automatisch mit <html> und <body>-Tags. Das liegt daran, dass libxml dafür konzipiert wurde, *vollständige* Seiten zu parsen, nicht nur einen Teil davon, wie wir es tun.

Eine mögliche Lösung ist das Setzen einiger Flags beim Laden des HTML, aber auch das ist nicht perfekt. Beim Laden des HTML erwartet libxml ein einziges Wurzelelement, aber Beiträge können mehr als ein Wurzelelement haben (normalerweise haben Sie viele Absätze). In diesem Fall wirft libxml einige Fehler.

Die bessere Lösung, die ich gefunden habe, ist das Erstellen einer Unterklasse von DOMDocument und das Überschreiben der Funktion saveHTML, um diese html- und body-Tags zu entfernen. Beim Laden des HTML setze ich nicht das Flag LIBXML_HTML_NOIMPLIED, damit keine Fehler auftreten.

Es ist nicht ideal, aber es erfüllt seinen Zweck.

class MSDOMDocument extends DOMDocument {
    public function saveHTML ( $node = null ) {
        $string = parent::saveHTML( $node );

        return str_replace( array( '<html><body>', '</body></html>' ), '', $string );
    }
}

Jetzt müssen wir MSDOMDocument anstelle von DOMDocument in unseren Filterfunktionen verwenden. Wenn Sie mehr als einen Filter erstellen möchten, rate ich Ihnen, den Beitrag nur einmal zu parsen und die MSDOMDocument-Instanz herumzureichen. Wenn alle Transformationen abgeschlossen sind, erhalten wir den HTML-String zurück.

function css_tricks_example_the_content( $content ) {

    // First encode all characters to their HTML entities
    $encoded = mb_convert_encoding( $content, 'HTML-ENTITIES', 'UTF-8' );

    // Load the content, suppressing warnings (libxml complains about not having
    // a root element (we have many paragraphs)
    $html = new MSDOMDocument();
    $ok = @$html->loadHTML( $encoded, LIBXML_HTML_NODEFDTD | LIBXML_NOBLANKS );

    // If it didn't parse the HTML correctly, do not proceed. Return the original, untransformed, post
    if ( !$ok ) {
        return $content;
    }

    // Pass the document to all filters
    css_tricks_content_filter_1( $html );
    css_tricks_content_filter_2( $html );

    // Filtering is done. Serialize the transformed post
    return $html->saveHTML();

}
add_filter( 'the_content', 'css_tricks_example_the_content' );

Beispiele für die Inhaltsänderung

Wir haben gelernt, dass wir Shortcodes und libxml verwenden können, um die Menge an HTML zu reduzieren, die wir direkt in den Beitrag einfügen müssen. Es kann etwas schwierig sein zu verstehen, welche Ergebnisse wir erzielen können, also gehen wir einige reale Beispiele durch.

Viele der folgenden Beispiele stammen aus der Produktionsversion von MacStories. Andere Beispiele sind Ideen von Chris, die eines Tages leicht zu CSS Tricks hinzugefügt werden könnten (oder bereits verwendet werden).

Zitat-Splitter

Ihre Website könnte Zitat-Splitter haben. Das gewünschte HTML könnte so etwas sein wie

<p>lorem ipsum dolor…</p>
<div class='pull-quote-wrapper'>
  <blockquote class='pull-quote-content'>This is the content of the pull quote</blockquote>
  <span class='pull-quote-author'>Author</span>
</div>
<p>lorem ipsum and the rest of the post</p>

Um so etwas zu erreichen, schlage ich einen Shortcode vor

function css_tricks_pull_quote_shortcode( $attrs, $content = null ) {

    extract( shortcode_atts( array(
        'author' => ''
    ), $attrs ) );

    $authorHTML = $author !== '' ? "<span class='pull-quote-author'>$author</span>" : '';

    return <<<HTML
<div class='pull-quote-wrapper'>
  <blockquote class='pull-quote-content'>$content</blockquote>
  $authorHTML
</div>
HTML;

}
add_shortcode( 'pullquote', 'css_tricks_pull_quote_shortcode' );

In Ihrem Beitrag würden Sie dann Folgendes tun

lorem ipsum dolor…

[pullquote author="Mr. Awesome"]This is the content of the pull quote[/pullquote]

lorem ipsum and the rest of the post

Der Autor ist optional und die Funktion, die ihn verarbeitet, lässt ihn komplett weg, wenn er nicht gesetzt ist.

Dafür gibt es viele Vorteile

  • Wenn Sie ein anderes HTML oder andere HTML-Klassen benötigen, können Sie die Ausgabe der Funktion an einer Stelle aktualisieren.
  • Wenn Sie Zitat-Splitter ganz streichen möchten, können Sie beginnen, einen leeren String aus der Funktion zurückzugeben.
  • Wenn Sie eine Funktion hinzufügen möchten (z. B. Klick zum Twittern), aktualisieren Sie die Ausgabe der Funktion.

Twitter/Instagram-Einbettungen

Eine der größten Funktionen von WordPress ist meiner Meinung nach die automatische Einbettung. Sagen wir, Sie möchten externen Inhalt in Ihren Beitrag einfügen: Die Chancen stehen gut, dass Sie das erledigt bekommen, indem Sie einfach die URL in eine eigene Zeile einfügen. Kein Suchen mehr nach dem richtigen Einbettungscode. Und am wichtigsten ist, dass Sie ihn nicht auf dem neuesten Stand halten müssen.

Das nennt man oEmbed, und die Liste der unterstützten Anbieter finden Sie hier und hier.

WordPress hat einen Hook, um solche Einbettungen anzupassen. Wenn Sie den eingebetteten Inhalt in eine div einwickeln möchten, können Sie Folgendes tun:

function macstories_wrap_embeds ( $return, $url, $attr ) {
    return <<<HTML
        <div class='media-wrapper'>$return</div>
HTML;
}
add_filter( 'embed_handler_html', 'macstories_wrap_embeds', 10, 3 );

function macstories_wrap_oembeds ( $cache, $url, $attr, $id ) {
    return <<<HTML
        <div class='media-wrapper'>$cache</div>
HTML;
}
add_filter( 'embed_oembed_html', 'macstories_wrap_oembeds', 10, 4 );

Syntaxhervorhebung

Sie können Ihre Codeblöcke serverseitig verarbeiten, um jeder Zeile Zeilennummern hinzuzufügen. Damit sollten Sie in der Lage sein, den Code einfach in pre- und code-Blöcken einzufügen.

Dies wird mit dem the_content-Filter und libxml erreicht

  1. Suche nach allen Codeblöcken
  2. Hole alle Zeilen, indem du auf Zeilenumbrüche splittest
  3. Umschließe jede Zeile mit einem span
  4. Anwendung von CSS

Der Handler ändert auch die Klassen (wie im früheren Beispiel erklärt), wie es der Syntax-Highlighter erfordert.

function css_tricks_code_blocks_add_line_numbers( $html ) {

    // Iterating a nodelist while manipulating it is not a good thing, because
    // the nodelist dynamically updates itself. Get all code elements and put
    // only the ones that are direct children of pre element in an array
    $codeBlocks = array();
    $nodes = $html->getElementsByTagName( 'code' );
    foreach ( $nodes as $node ) {
        if ( $node->parentNode->nodeName == 'pre' ) {
            $codeBlocks[] = $node;
        }
    }

    foreach ( $codeBlocks as $code ) {

        // Fix HTML classes
        $lang = $code->getAttribute( 'lang' );
        $code->removeAttribute( 'lang' );
        if ( $lang === 'js' ) {
            $code->setAttribute( 'class', 'language-javascript' );
        }
        // Probably add some more `else if` blocks...

        // Get the actual code snippet
        $snippet = $code->textContent;

        // Split in lines
        $lines = explode("\n", $snippet);

        // Remove all code
        $code->nodeValue = '';

        // Each line must be wrapped in its own element. Encode entities to be
        // sure that libxml doesn't complain
        foreach ( $lines as $line ) {
            $wrapper = $html->createElement('span');
            $wrapper->setAttribute( 'class', 'code-line' );

            // Create a text node, to have full escaping support
            $textNode = $html->createTextNode( $line . "\n" );

            // Add the text to span
            $wrapper->appendChild( $textNode );

            // Add the span to code
            $code->appendChild( $wrapper );
        }

        // Jetpack adds a newline at the end of the code block. Remove that
        if ( $code->lastChild->textContent == '' ) {
            $code->removeChild( $code->lastChild );
        }

    }

}

Sie können CSS-Zähler verwenden, um die Nummern zu generieren

.code-line {
    display: block;
    counter-increment: line-number;

    &::before {
        content: counter(line-number);
        display: inline-block;
        width: 30px;
        margin-right: 10px;
    }
}

Ein reales Beispiel von MacStories ist, dass wir Markdown so schreiben können:

```js
// This is a JS code block
var string = "hello";
var what = "world";
var unusedVar = 3;
alert(string + " " + what); // Actually do something
```

Was zu HTML verarbeitet wird, dann durch diesen Filter geht und so endet:

<pre><code class='javascript'><span class='code-line'>// This is a JS code block</span>
<span class='code-line'>var string = "hello";</span>
<span class='code-line'>var what = "world";</span>
<span class='code-line'>var unusedVar = 3;</span>
<span class='code-line'>alert(string + " " + what); // Actually do something</span></code></pre>

Was so gerendert wird, mit unserem Syntax-Highlighter

URLs umschreiben

Als wir bei MacStories zu HTTPS wechselten, hatten wir ein Problem mit gemischten Inhalt-Warnungen. Alte Beiträge verlinkten auf Bilder, die auf Rackspace gehostet wurden, über das HTTP-Protokoll. Ups.

Glücklicherweise bedient Rackspace Inhalte auch über HTTPS, aber die URL ist etwas anders.

Wir beschlossen, einen Filter hinzuzufügen, um diese URLs zu ändern. Redakteure werden Bilder über die HTTPS-URL verlinken, aber dieser Filter kann versehentlich eingefügte HTTP-URLs umgehen. Tschüss gemischte Inhalt-Warnungen.

Dies wird durch Hinzufügen eines the_content-Filters und Ausführen einer regulären Ausdruckersetzung erreicht.

function macstories_rackspace_http_to_https( $content ) {
    return preg_replace(
        '/http:\/\/([A-z0-9]+-[A-z0-9]+\.)r[0-9]{1,2}(\.cf1\.rackcdn\.com\/)/i',
        'https://$1ssl$2',
        $content
    );
}

Sie können etwas Ähnliches tun, um Bildlinks zu CDN zu machen: Wenn Ihre Bild-URLs ein gut definiertes Muster haben (damit Sie keine URL von etwas ändern, das kein Bild ist), verwenden Sie einen ähnlichen Ansatz. Andernfalls ist es besser, das HTML zu parsen, um nur das src-Attribut der Bilder zu ändern.

IDs zu Überschriften hinzufügen

Wenn das id-Attribut für alle Überschriften gesetzt ist, können Sie zu einem bestimmten Abschnitt verlinken (z. B. wenn Sie ein Inhaltsverzeichnis haben oder einen Link teilen möchten, der zum richtigen Abschnitt gescrollt wird).

Wenn Sie in HTML schreiben, können Sie sie manuell hinzufügen. Aber das ist mühsam. Wenn Sie in Markdown schreiben, müssen Sie sicherstellen, dass Ihr Markdown-Prozessor sie hinzufügt (Jetpack tut dies nicht). In jedem Fall fügt das Verfassen eine Redundanz zu Ihrem Inhalt hinzu.

Sie können den Prozess mit libxml in einem the_content-Filter automatisieren

  1. Suche nach allen Überschriften
  2. Generiere den Slug
  3. Setze diesen Slug als id-Attribut

Der Filter ist dieser

function css_tricks_add_id_to_headings( $html ) {

    // Store all headings of the post in an array
    $tagNames = array( 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' );
    $headings = array();
    $headingContents = array();
    foreach ( $tagNames as $tagName ) {
        $nodes = $html->getElementsByTagName( $tagName );
        foreach ( $nodes as $node ) {
            $headings[] = $node;
            $headingContents[ $node->textContent ] = 0;
        }
    }

    foreach ( $headings as $heading ) {

        $title = $heading->textContent;

        if ( $title === '' ) {
            continue;
        }

        $count = ++$headingContents[ $title ];

        $suffix = $count > 1 ? "-$count" : '';

        $slug = sanitize_title( $title );
        $heading->setAttribute( 'id', $slug . $suffix );
    }

}

Dieser Filter verhindert auch die Erzeugung doppelter ids.

Umschließende Absätze entfernen

Wenn die automatische Einbettung meine Lieblingsfunktion von WordPress ist, dann ist die automatische Absatzumwandlung das, was ich am meisten hasse. Dieses Problem ist gut bekannt.

Reguläre Ausdrücke zu verwenden, um sie zu entfernen, funktioniert, ist aber nicht gut geeignet für die Arbeit mit HTML-Tags. Wir können libxml verwenden, um die umschließenden Absätze von Bildern und anderen Elementen wie picture, video, audio und iframe zu entfernen.

function css_tricks_content_remove_wrapping_p( $html ) {

    // Iterating a nodelist while manipulating it is not a good thing, because
    // the nodelist dynamically updates itself. Get all things that must be
    // unwrapped and put them in an array.
    $tagNames = array( 'img', 'picture', 'video', 'audio', 'iframe' );
    $mediaElements = array();
    foreach ( $tagNames as $tagName ) {
        $nodes = $html->getElementsByTagName( $tagName );
        foreach ( $nodes as $node ) {
            $mediaElements[] = $node;
        }
    }

    foreach ( $mediaElements as $element ) {

        // Get a reference to the parent paragraph that may have been added by
        // WordPress. It might be the direct parent node or the grandparent
        // (LOL) in case of links
        $paragraph = null;

        // Get a reference to the image itself or to the link containing the
        // image, so we can later remove the wrapping paragraph
        $theElement = null;

        if ( $element->parentNode->nodeName == 'p' ) {
            $paragraph = $element->parentNode;
            $theElement = $element;
        } else if ( $element->parentNode->nodeName == 'a' &&
                $element->parentNode->parentNode->nodeName == 'p' ) {
            $paragraph = $element->parentNode->parentNode;
            $theElement = $element->parentNode;
        }

        // Make sure the wrapping paragraph only contains this child
        if ( $paragraph && $paragraph->textContent == '' ) {
            $paragraph->parentNode->replaceChild( $theElement, $paragraph );
        }
    }

}

rel=noopener hinzufügen

Vor kurzem wurden wir auf ein Sicherheitsproblem bezüglich Links aufmerksam, die sich in einem neuen Tab öffnen.

Das Hinzufügen des Attributs rel=noopener behebt das Problem, aber das ist nichts, woran sich Redakteure erinnern sollten. Es ist auch nicht gut mit Markdown vereinbar, da Sie Links in reinem HTML schreiben müssten.

libxml kann uns helfen

function css_tricks_rel_noopener( $html ) {

    $nodes = $html->getElementsByTagName( 'a' );
    foreach ( $nodes as $node ) {
        $node->setAttribute( 'rel', 'noopener' );
    }

}

Überlegungen

Ich verwende die oben erklärten Techniken seit dem Start von MacStories 4 und hatte keine größeren Probleme. Redakteure können sich ausschließlich auf das Schreiben großartiger Inhalte konzentrieren. Alle *präsentationsbezogenen* Transformationen/Generierungen sind im Code dokumentiert und können leicht auf die neue Version portiert oder an das neue Design angepasst werden. Das ist ein großer Gewinn. Ich muss keine `legacy-theme.css`-Datei erstellen, um alte (und schlechte) Entscheidungen zu gestalten oder zu beheben.

Mit Content-Filtern können Sie so ziemlich alles tun. Bei Shortcodes müssen Sie vorsichtig sein, um keine übermäßig spezialisierten Shortcodes zu erstellen, die wie das alte rohe HTML aussehen, das Sie früher hatten. Zum Beispiel

[bad-shortcode align="left" color="blue" font="georgia"]…[/bad-shortcode]

Einige dieser Attribute machen in Zukunft vielleicht keinen Sinn mehr, daher liegt es an Ihnen, Attribute zu wählen, die gut geeignet und abstrakt genug erscheinen, um ewig zu bestehen. Dennoch ist selbst ein schlechter Shortcode besser als keine Inhaltsabstraktion.

Am Ende: Tun Sie, was Sie für richtig halten, und denken Sie zweimal nach, bevor Sie implementieren. Fragen Sie sich immer: "Werde ich das brauchen, wenn das nächste Design live geht?"