`. WordPress,
* der Block-Editor und die gaengigen Highlighter erwarten dagegen
* ``.
*
* Die verwalteten Bereiche fuer Hub und Quellen kommen jeweils in einen
* core/html-Block. Ohne ihn wickelt der Block-Editor beim Speichern jeden
* Marker in einen eigenen Absatz (``); der
* Bereich zwischen den Markern laege dann quer zu den Absatzgrenzen, und der
* naechste Hub-Patch schriebe eine Liste in einen Absatz. Den Inhalt eines
* core/html-Blocks speichert der Editor dagegen byte-genau. Im Browser
* nachgewiesen am 2026-10-20 (WordPress 7.1).
*/
final class HtmlNormalizer {
/**
* Die verwalteten Bereiche: Start- und Endmarker.
*
* @var array
*/
private const MANAGED_REGIONS = array(
array( '', '' ),
array( '', '' ),
);
/**
* Wandelt den Body fuer den Import um.
*
* Laeuft nur beim Import. Der Server baut spaetere Patches gegen den
* zurueckgemeldeten Body, also gegen genau diese Form.
*
* @param string $html Gelieferter Body.
* @return string
*/
public static function for_import( string $html ): string {
$converted = preg_replace_callback(
'#]*)?>(.*?)
#is',
array( self::class, 'convert_pre' ),
$html
);
$html = null === $converted ? $html : $converted;
foreach ( self::MANAGED_REGIONS as $markers ) {
$html = self::wrap_region( $html, $markers[0], $markers[1] );
}
return $html;
}
/**
* Setzt einen verwalteten Bereich in einen core/html-Block.
*
* Nur wenn beide Marker genau einmal und in der richtigen Reihenfolge
* vorkommen; sonst bleibt der Body, wie er ist. Das Format ist genau das,
* was der Editor selbst schreibt — ein Zeilenumbruch nach dem Oeffner, einer
* vor dem Schliesser, Bloecke durch eine Leerzeile getrennt —, damit ein
* Speichern im Editor den Bereich nicht veraendert.
*
* @param string $html Body.
* @param string $start Startmarker.
* @param string $end Endmarker.
* @return string
*/
private static function wrap_region( string $html, string $start, string $end ): string {
if ( 1 !== substr_count( $html, $start ) || 1 !== substr_count( $html, $end ) ) {
return $html;
}
$from = (int) strpos( $html, $start );
$to = (int) strpos( $html, $end ) + strlen( $end );
if ( $to <= $from ) {
return $html;
}
$before = rtrim( substr( $html, 0, $from ) );
$region = substr( $html, $from, $to - $from );
$after = ltrim( substr( $html, $to ) );
$block = "\n" . $region . "\n";
return implode( "\n\n", array_filter( array( $before, $block, $after ), 'strlen' ) );
}
/**
* Wandelt einen einzelnen pre-Block um.
*
* @param array $parts Treffer: [0] ganzer Block, [1] Attribute, [2] Inhalt.
* @return string
*/
private static function convert_pre( array $parts ): string {
$attributes = $parts[1] ?? '';
$inner = $parts[2] ?? '';
// Steckt der Code schon in , gibt es nichts zu tun.
if ( 1 === preg_match( '#^\s*]#i', $inner ) ) {
return $parts[0];
}
$language = '';
if ( 1 === preg_match( '#data-highlighter=(["\'])([a-z0-9_+\#.-]*)\1#i', $attributes, $language_match ) ) {
$language = strtolower( $language_match[2] );
}
$code_attributes = '' !== $language ? ' class="language-' . esc_attr( $language ) . '"' : '';
return '' . $inner . '
';
}
}