Úprava chování prvků

Tato kapitola popisuje, jak můžete změnit chování existujících prvků v Texy – například upravit, jak se zpracovávají obrázky, odkazy nebo formátování. Pokud chcete přidat zcela novou syntaxi, kterou Texy standardně nezná, přečtěte si kapitolu Přidání vlastní syntaxe.

Představte si, že chcete, aby standardní syntaxe pro obrázky [* URL *] rozpoznávala speciální adresu [* youtube:dQw4w9WgXcQ *] a místo běžného obrázku vytvořila embedded přehrávač.

Nebo chcete obarvovat výpisy zdrojového kódu pomocí syntax highlighteru. A tak dále. Přesně k tomu slouží element handlery – funkce, které Texy volá při zpracování konkrétních prvků. Například zaregistrujete handler pro element image, který zkontroluje URL, a pokud začíná youtube:, vrátí iframe místo standardního obrázku. Neměníte syntaxi, jen upravujete, co se s nalezenou konstrukcí stane.

Elementy a jejich handlery

V terminologii Texy je element název pro typ prvku, který může být v dokumentu zpracován. Například image je element pro obrázky, linkURL pro odkazy, viz výchozí elementy. Každý element má svůj výchozí handler, který je implementován v příslušném modulu a stará se o standardní zpracování.

Když napíšete v textu [* image.jpg *], parser najde tuto syntaxi, vytvoří objekt Texy\Image s daty o obrázku a zavolá všechny handlery zaregistrované pro element image. Pokud žádný vlastní handler není, zavolá se pouze výchozí handler z ImageModule, který vytvoří HTML tag <img>.

Handler zaregistrujete voláním metody addHandler():

$texy->addHandler('image', function(
	Texy\HandlerInvocation $invocation,
	Texy\Image $image,
	?Texy\Link $link,
) {
	// zde bude vaše logika
});

První parametr je název elementu, druhý je callback funkce. Callback dostává jako první parametr vždy objekt Texy\HandlerInvocation, následují parametry jsou specifické pro daný element.

Podrobné vysvětlení všech typů handlerů najdete v kapitole Architektura a principy.

Jak funguje zpracování

Když Texy potřebuje zpracovat element, vytvoří objekt HandlerInvocation obsahující všechny zaregistrované handlery pro tento typ prvku. Váš handler se zavolá jako první a může:

  • Delegovat na další handler voláním $invocation->proceed()
  • Upravit vstup voláním proceed() s modifikovanými parametry
  • Upravit výstup zpracováním výsledku z proceed()
  • Přerušit řetěz vrácením vlastního výsledku bez volání proceed()

Metoda proceed() posune zpracování na další handler v řetězu. Pokud už žádný vlastní handler není, zavolá se výchozí implementace z modulu. To znamená, že váš handler má absolutní kontrolu – může rozhodnout, zda se vůbec zavolá výchozí logika.

Tento mechanismus se nazývá chain of responsibility (řetěz zodpovědnosti):

$texy->addHandler('image', function(
	Texy\HandlerInvocation $invocation,
	Texy\Image $image,
	?Texy\Link $link,
) {
	// 1. Upravíme vstupní data před zpracováním
	$image->modifier->title = 'Modified title';

	// 2. Zavoláme další handler nebo výchozí zpracování
	$element = $invocation->proceed($image, $link);

	// 3. Upravíme výsledný HTML element
	$element->attrs['loading'] = 'lazy';

	return $element;
});

Pořadí vykonávání je od posledně registrovaného k prvnímu. Pokud modul zaregistruje svůj výchozí handler při konstrukci a vy pak zaregistrujete vlastní handler, váš handler se zavolá první. To vám umožňuje přepsat nebo obalit výchozí chování.

Výchozí elementy

Texy poskytuje několik předpřipravených elementů, pro které můžete registrovat vlastní handlery. Zde je jejich kompletní seznam s parametry, které dostává handler.

image

Zpracovává obrázky.

function(
	Texy\HandlerInvocation $invocation,
	Texy\Image $image,
	?Texy\Link $link,
): Texy\HtmlElement|string|null

Parametr $image obsahuje URL, rozměry a modifikátory. Parametr $link je zadán, pokud je obrázek odkazem (syntaxe [* img *]:url).

linkReference

Zpracovává referenční odkazy typu [ref].

function(
	Texy\HandlerInvocation $invocation,
	Texy\Link $link,
	string $content,
): Texy\HtmlElement|string|null

Parametr $link obsahuje URL a modifikátory načtené z definice reference. Parametr $content je HTML obsah odkazu (již zpracovaný parsováním inline syntaxí).

linkEmail

Zpracovává automaticky rozpoznané emailové adresy v textu.

function(
	Texy\HandlerInvocation $invocation,
	Texy\Link $link,
): Texy\HtmlElement|string|null

Parametr $link obsahuje emailovou adresu v property URL.

linkURL

Zpracovává automaticky rozpoznané URL v textu.

function(
	Texy\HandlerInvocation $invocation,
	Texy\Link $link,
): Texy\HtmlElement|string|null

Parametr $link obsahuje nalezenou URL.

phrase

Zpracovává inline formátování.

function(
	Texy\HandlerInvocation $invocation,
	string $phrase,
	string $content,
	Texy\Modifier $modifier,
	?Texy\Link $link,
): Texy\HtmlElement|string|null

Parametr $phrase je název syntaxe jako phrase/strong nebo phrase/em. Parametr $content je text uvnitř formátování. Parametr $modifier obsahuje CSS třídy, styly a další modifikátory. Parametr $link je zadán, pokud má formátování připojený odkaz.

newReference

Volá se, když parser najde referenci, která není definovaná.

function(
	Texy\HandlerInvocation $invocation,
	string $name,
): Texy\HtmlElement|string|null

Parametr $name je název reference. Handler může vytvořit odkaz dynamicky nebo vrátit null pro odmítnutí.

htmlComment

Zpracovává HTML komentáře.

function(
	Texy\HandlerInvocation $invocation,
	string $content,
): string

Parametr $content je text mezi <!-- a -->.

htmlTag

Zpracovává HTML tagy v textu.

function(
	Texy\HandlerInvocation $invocation,
	Texy\HtmlElement $el,
	bool $isStart,
	?bool $forceEmpty,
): Texy\HtmlElement|string|null

Parametr $el je element s názvem a atributy. Parametr $isStart určuje, zda jde o otevírací tag. Parametr $forceEmpty vynutí prázdný element.

script

Zpracovává skripty {{command: args}}.

function(
	Texy\HandlerInvocation $invocation,
	string $command,
	array $args,
	?string $raw,
): Texy\HtmlElement|string|null

Parametr $command je název příkazu. Parametr $args je pole argumentů. Parametr $raw je původní neparsovaný řetězec argumentů.

figure

Zpracovává obrázky s popiskou.

function(
	Texy\HandlerInvocation $invocation,
	Texy\Image $image,
	?Texy\Link $link,
	string $content,
	Texy\Modifier $modifier,
): Texy\HtmlElement|null

Parametr $content je text popisky pod obrázkem.

heading

Zpracovává nadpisy.

function(
	Texy\HandlerInvocation $invocation,
	int $level,
	string $content,
	Texy\Modifier $modifier,
	bool $isSurrounded,
): Texy\HtmlElement

Parametr $level je úroveň nadpisu (0–6). Parametr $content je text nadpisu. Parametr $isSurrounded určuje, zda jde o ohraničený nadpis (###) nebo podtržený.

horizline

Zpracovává horizontální čáry.

function(
	Texy\HandlerInvocation $invocation,
	string $type,
	Texy\Modifier $modifier,
): Texy\HtmlElement

Parametr $type je řetězec znaků použitých pro čáru (--- nebo ***).

block

Zpracovává speciální bloky /--type\--.

function(
	Texy\HandlerInvocation $invocation,
	string $blocktype,
	string $content,
	?string $param,
	Texy\Modifier $modifier,
): Texy\HtmlElement|string

Parametr $blocktype je typ bloku s prefixem block/, např. block/code nebo block/html. Parametr $content je obsah bloku. Parametr $param je volitelný parametr za typem (např. jazyk u kódu).

emoticon

Zpracovává emotikony (smajlíky).

function(
	Texy\HandlerInvocation $invocation,
	string $emoticon,
	string $raw,
): Texy\HtmlElement|string

Parametr $emoticon je rozpoznaný emotikon (např. :-) nebo :-( ). Parametr $raw je původní text včetně případných opakujících se znaků (např. :-))))) ).

Emotikony jsou ve výchozím nastavení vypnuté. Zapnete je pomocí $texy->allowed['emoticon'] = true;

Výchozí eventy

Texy poskytuje několik předpřipravených eventů, pro které můžete registrovat handlery. Říká se jim notification handlery. Na rozdíl od element handlerů tyto handlery nic nevrací. Používají se pro vedlejší efekty jako logování, sběr statistik nebo úpravy již vytvořeného DOM stromu.

beforeParse

Volá se před začátkem parsování textu. Umožňuje provést předzpracování nebo načíst definice.

function(
	Texy\Texy $texy,
	string &$text,
	bool $isSingleLine,
): void

Parametr $text je předán referencí, takže ho můžete upravit. Parametr $isSingleLine určuje, zda se parsuje jeden řádek nebo celý dokument.

afterParse

Volá se po dokončení parsování, před konverzí DOM stromu na HTML. Umožňuje upravit vytvořený DOM.

function(
	Texy\Texy $texy,
	Texy\HtmlElement $DOM,
	bool $isSingleLine,
): void

Parametr $DOM je kořenový element dokumentu, který můžete procházet a upravovat.

afterList

Volá se po vytvoření seznamu (číslovaného nebo nečíslovaného).

function(
	Texy\BlockParser $parser,
	Texy\HtmlElement $element,
	Texy\Modifier $modifier,
): void

Parametr $element je vytvořený element <ul> nebo <ol>. Parametr $modifier obsahuje modifikátory aplikované na celý seznam.

afterDefinitionList

Volá se po vytvoření definičního seznamu.

function(
	Texy\BlockParser $parser,
	Texy\HtmlElement $element,
	Texy\Modifier $modifier,
): void

Parametr $element je vytvořený element <dl>.

afterTable

Volá se po vytvoření tabulky.

function(
	Texy\BlockParser $parser,
	Texy\HtmlElement $element,
	Texy\Modifier $modifier,
): void

Parametr $element je vytvořený element <table>.

afterBlockquote

Volá se po vytvoření citace.

function(
	Texy\BlockParser $parser,
	Texy\HtmlElement $element,
	Texy\Modifier $modifier,
): void

Parametr $element je vytvořený element <blockquote>.

Základní použití

Nejjednodušší element handler jen deleguje na výchozí zpracování:

$texy->addHandler('image', function(
	Texy\HandlerInvocation $invocation,
	Texy\Image $image,
	?Texy\Link $link,
) {
	return $invocation->proceed();
});

Tento handler nic nemění, ale ukazuje základní kostru. Všechny parametry předá dál a vrátí výsledek.

Úprava vstupních dat

Handler může upravit data před jejich zpracováním:

$texy->addHandler('image', function(
	Texy\HandlerInvocation $invocation,
	Texy\Image $image,
	?Texy\Link $link,
) {
	// přidáme default rozměry, pokud nejsou zadané
	$image->width ??= 800;
	$image->height ??= 600;
	return $invocation->proceed();
});

Změny provedené na objektech $image nebo $link se projeví v dalším zpracování, včetně výchozího handleru.

Úprava výstupního elementu

Handler může upravit HTML element vrácený z proceed():

$texy->addHandler('image', function(
	Texy\HandlerInvocation $invocation,
	Texy\Image $image,
	?Texy\Link $link,
) {
	$element = $invocation->proceed();

	if ($element) {
		// přidáme lazy loading
		$element->attrs['loading'] = 'lazy';

		// přidáme CSS třídu
		$element->attrs['class'][] = 'responsive';
	}

	return $element;
});

Podmíněné zpracování

Handler může zpracovat pouze určité případy a ostatní delegovat:

$texy->addHandler('image', function(
	Texy\HandlerInvocation $invocation,
	Texy\Image $image,
	?Texy\Link $link,
) {
	// speciální zpracování pro YouTube videa
	if (str_starts_with($image->URL, 'youtube:')) {
		$id = substr($image->URL, 8);
		$iframe = sprintf(
			'<iframe src="https://youtube.com/embed/%s"></iframe>',
			htmlspecialchars($id)
		);
		return $invocation->getTexy()
			->protect($iframe, Texy\Texy::CONTENT_BLOCK);
	}

	// ostatní obrázky zpracujeme standardně
	return $invocation->proceed();
});

Přerušení zpracování

Handler může odmítnout zpracování vrácením null:

$texy->addHandler('image', function(
	Texy\HandlerInvocation $invocation,
	Texy\Image $image,
	?Texy\Link $link,
) {
	// zakážeme externí obrázky
	if (str_contains($image->URL, '://')) {
		return null;
	}

	return $invocation->proceed();
});

Praktické příklady

Následující příklady ukazují reálné use-case pro element handlery.

YouTube embed

Převod speciální syntaxe na embedded video:

$texy->addHandler('image', function(
	Texy\HandlerInvocation $invocation,
	Texy\Image $image,
	?Texy\Link $link,
) {
	if (str_starts_with($image->URL, 'youtube:')) {
		$id = substr($image->URL, 8);
		$width = $image->width ?: 560;
		$height = $image->height ?: 315;

		$iframe = sprintf(
			'<iframe width="%d" height="%d" '
			. 'src="https://youtube.com/embed/%s" '
			. 'frameborder="0" allowfullscreen></iframe>',
			$width, $height, htmlspecialchars($id)
		);

		$texy = $invocation->getTexy();
		return $texy->protect($iframe, $texy::CONTENT_BLOCK);
	}

	return $invocation->proceed();
});

Použití v textu:

[* youtube:dQw4w9WgXcQ 640x360 *]

Galerie obrázků

Obalení obrázků do speciálního divu pro lightbox:

$texy->addHandler('image', function(
	Texy\HandlerInvocation $invocation,
	Texy\Image $image,
	?Texy\Link $link,
) {
	$element = $invocation->proceed();

	// pokud má obrázek třídu 'gallery'
	if (isset($image->modifier->classes['gallery'])) {
		// obalíme do divu s lightbox atributy
		$wrapper = new Texy\HtmlElement('div');
		$wrapper->attrs['class'][] = 'lightbox-item';
		$wrapper->attrs['data-src'] = $image->URL;
		$wrapper->add($element);

		return $wrapper;
	}

	return $element;
});

Použití:

[* image.jpg .[gallery] *]

Validace odkazů

Kontrola, zda odkazy nalezené v textu vedou na povolené domény:

$allowedDomains = ['example.com', 'trusted.org'];

$texy->addHandler('linkURL', function(
	Texy\HandlerInvocation $invocation,
	Texy\Link $link,
) use ($allowedDomains) {
	$host = parse_url($link->URL, PHP_URL_HOST);

	// pokud doména není v whitelistu, nepovolíme odkaz
	if ($host && !in_array($host, $allowedDomains, true)) {
		return null;
	}

	return $invocation->proceed();
});

Automatické rel=„nofollow“

Přidání nofollow všem externím odkazům nalezeným v textu:

$texy->addHandler('linkURL', function(
	Texy\HandlerInvocation $invocation,
	Texy\Link $link,
) {
	$element = $invocation->proceed();

	// pokud odkaz obsahuje // (tedy je externí)
	if (str_contains($link->URL, '://')) {
		$element->attrs['rel'] = 'nofollow';
	}

	return $element;
});

Syntax highlighting

Integrace knihovny pro zvýraznění syntaxe:

$texy->addHandler('block', function(
	Texy\HandlerInvocation $invocation,
	string $blocktype,
	string $content,
	?string $param,
	Texy\Modifier $modifier,
) {
	// zpracujeme pouze bloky typu 'code'
	if ($blocktype !== 'block/code') {
		return $invocation->proceed();
	}

	// aplikujeme syntax highlighting
	$highlighter = new MyHighlighter();
	$highlighted = $highlighter->highlight($content, $param);

	$el = new Texy\HtmlElement('pre');
	$modifier->decorate($invocation->getTexy(), $el);
	$el->attrs['class'][] = 'language-' . $param;

	$code = new Texy\HtmlElement('code');
	$code->add($highlighted);
	$el->add($code);

	return $el;
});

Lazy loading

Projdeme všechny obrázky a přidáme lazy loading:

$texy->addHandler('afterParse', function(
	Texy\Texy $texy,
	Texy\HtmlElement $DOM,
	bool $isSingleLine,
) {
	foreach ($DOM->getIterator() as $child) {
		if ($child instanceof Texy\HtmlElement
			&& $child->getName() === 'img'
		) {
			$child->attrs['loading'] = 'lazy';
		}
	}
});

Logování použitých prvků

Sběr statistik o použitých prvcích v dokumentu:

$stats = [];

$texy->addHandler('beforeParse', function(
	Texy\Texy $texy,
	string &$text,
	bool $isSingleLine,
) use (&$stats) {
	$stats = ['images' => 0, 'links' => 0, 'headings' => 0];
});

$texy->addHandler('image', function(
	Texy\HandlerInvocation $invocation,
	Texy\Image $image,
	?Texy\Link $link,
) use (&$stats) {
	$stats['images']++;
	return $invocation->proceed();
});

$texy->addHandler('linkURL', function(
	Texy\HandlerInvocation $invocation,
	Texy\Link $link,
) use (&$stats) {
	$stats['links']++;
	return $invocation->proceed();
});

$texy->addHandler('heading', function(
	Texy\HandlerInvocation $invocation,
	int $level,
	string $content,
	Texy\Modifier $modifier,
	bool $isSurrounded,
) use (&$stats) {
	$stats['headings']++;
	return $invocation->proceed();
});

Pomocné třídy

Při práci s handlery budete pracovat s několika důležitými třídami. Zde je jejich přehled s nejdůležitějšími vlastnostmi.

Texy\Image

Reprezentuje obrázek s jeho parametry:

$image->URL;           // string - cesta k obrázku
$image->linkedURL;     // ?string - URL odkazu (pokud je obrázek odkazem)
$image->width;         // ?int - šířka v pixelech
$image->height;        // ?int - výška v pixelech
$image->asMax;         // bool - zda jsou rozměry maximální
$image->modifier;      // Modifier - CSS třídy, styly, atributy
$image->name;          // ?string - název reference

Reprezentuje odkaz s jeho parametry:

$link->URL;        // string - cílová URL
$link->raw;        // string - původní URL (před normalizací)
$link->modifier;   // Modifier - CSS třídy, styly, atributy
$link->type;       // string - typ odkazu (COMMON, BRACKET, IMAGE)
$link->label;      // ?string - text odkazu (u referencí)
$link->name;       // ?string - název reference

Konstanty pro typ odkazu:

Texy\Link::COMMON;   // běžný odkaz
Texy\Link::BRACKET;  // referenční odkaz [ref]
Texy\Link::IMAGE;    // odkaz z obrázku [* img *]

Texy\HtmlElement

Reprezentuje HTML element s jeho atributy a obsahem:

$el = new Texy\HtmlElement('div');

// práce s názvem elementu
$el->getName();          // vrací 'div'
$el->setName('section'); // změní na 'section'

// práce s atributy
$el->attrs['id'] = 'main';
$el->attrs['class'][] = 'container';
$el->attrs['style']['color'] = 'red';

// práce s obsahem
$el->setText('text');              // nastaví textový obsah
$el->getText();                    // vrací textový obsah
$el->add($child);                  // přidá potomka
$el->insert(0, $child);            // vloží potomka na pozici

// parsování obsahu
$el->parseLine($texy, $text);      // parsuje inline text
$el->parseBlock($texy, $text);     // parsuje blokový text

// konverze na HTML
$el->toString($texy);              // internal reprezentace
$el->toHtml($texy);                // finální HTML

Texy\Modifier

Reprezentuje modifikátory CSS tříd, stylů a atributů:

$mod->id;          // ?string - HTML id
$mod->classes;     // array - pole CSS tříd
$mod->styles;      // array - pole CSS stylů
$mod->attrs;       // array - HTML atributy
$mod->hAlign;      // ?string - horizontální zarovnání (left, right, center, justify)
$mod->vAlign;      // ?string - vertikální zarovnání (top, middle, bottom)
$mod->title;       // ?string - title atribut nebo alt pro obrázky

// aplikace modifikátoru na element
$mod->decorate($texy, $element);