RemexHtml/nl
Introductie
RemexHtml is een parser voor HTML5, geschreven in PHP.
Uitgangspunten RemexHtml:
- Modulair en flexibel.
- Snel, liever dan elegant. Voorbeeld: we gebruiken soms directe toegang in plaats van accessors, en programmeren zelf handmatig de code als die performance gevoelig is.
- Robuust, het streven is de slechts denkbare performance helemaal te voorkomen.
RemexHtml bevat deze modules:
- Een soepele preprocessor en tokenizer. Dit zorgt voor een 'token event stream'.
- Soepele boomstructuuropbouw, inclusief foutherstel. Dit zorgt voor een goed doorlopende aanpassing van de boomstructuur.
- Een snelle geïntegreerde HTML-serializer, passend bij het HTML fragment serialisatie algoritme.
- DOMDocument opbouw.
RemexHtml huidige gebreken:
- Encoding ondersteuning. Er wordt vanuit gegaan dat de invoer geldige UTF-8 is.
- Scripting.
- XHTML serialisatie.
- Precieze volgen van de bij bepaalde fouten gegenereerde uitvoer van de parser.
RemexHtml wil de W3C aanbevelingen van HTML 5.1 volgen, er zijn wat uitzonderingen voor wat kleine bugfixes voor het ondersteunen van oudere versies van HTML. Er is gekozen om de W3C standaard te implementeren en niet de laatste WHATWG draft omdat we streven naar stabiliteit en niet naar volledigheid van functionaliteit.
RemexHtml voldoet aan alle html5lib testen, met uitzondering van het tellen van de parse fouten en testen waarin gekeken wordt of aan de toekomstige versie van de standaard wordt voldaan.
Installatie
In MediaWiki
RemexHtml is als een core composer afhankelijkheid vanaf MediaWiki 1.29 beschikbaar. Het oorspronkelijke doel was het om een vervanging te zijn van HTML Tidy. Uitvoer van de wikitext parser wordt gestuurd naar de RemexHtml's HTML parser en de HTML5 tag gebruik wordt opgeschoond. De Tokenizer component wordt nu ook gebruikt voor tag verwijdering in Sanitizer.
Het wordt ook gebruikt voor het vooraf verwerken van HTML in de extensies Collection , TEI en Wikibase .
Buiten de Mediawiki
Installeer het wikimedia/remex-html package vanaf Packagist:
composer require wikimedia/remex-html
De gebruikte versienaamgeving is semantisch. De hoofdversie wordt verhoogd als de wijziging er een is die niet meer compatibel is de voorafgaande versie.
Overzicht architectuur
Voor een volledige documentatie, bekijk de uit de broncode samengestelde documentatie of de broncode zelf.
RemexHtml gebruikt een 'pipeline model'. Als er een gebeurtenis gemaakt moet worden, dan wordt er een toegevoegd callback object aangeroepen die de gebeurtenis afhandelt. De stappen zijn:
- Tokenizer
- Geeft een stroom van tekens uit de HTML. Voert de tokenization uit, zoals beschreven in het tokenisatie hoofdstuk in de HTML specificatie.
- Dispatcher
- Volgt de insertie mode, en geeft token gebeurtenissen door aan een specifieke handler voor de huidige insertie mode. Elke mode heeft een eigen class, met methoden voor elk token type.
- TreeBuilder
- Een helper class voor de insertie modes. Het volgt de status van de boomconstructie, ontvangt verzoeken voor aanpassingen in de boom van de insertie mode classes en verstuurd de boomaanpassingen.
In de HTML-specificatie wordt de boomstructuur voorgesteld als sterk geïntegreerd met de aanmaak van de DOM gegevensstructuur. Een grote innovatie van RemexHtml is het scheiden van de boomopbouw in een fase waar de wijzigingen van boom worden doorgegeven en een fase waarin de datastructuur echt wordt gemaakt. RemexHtml kan het wijzigen van de boom direct op volgorde brengen in de uitvoer zonder de hele DOM in het geheugen te hebben.
- Serializer
- Geeft HTML uit een stroom gebeurtenissen met aanpassingen aan de boom.
- DOMBuilder
- Geeft een intern native PHP DOMDocument uit een stroom gebeurtenissen met aanpassingen aan de boom.
Bij het gebruiken van Serializer is er nog een afsluitende fase:
- Formatter
- De interface Formatter converteert de SerializerNode objecten naar strings. Het is een helper voor Serializer waardoor de geproduceerde HTML eenvoudig kan worden aangepast. Serializer is complex en kent statussen, waar de Formatter subclasses in het algemeen geen status hebben, anders dan voor configuratie.
RemexHtml heeft ook:
- DOMSerializer
- een utility class voor het serialiseren van een DOM binnen een DOMBuilder, met een interface dat vergelijkbaar is met Serializer.
- PropGuard
- Veel RemexHtml classes gebruiken de PropGuard behandeling, wat voorkomt dat er per ongeluk een niet bestaande property wordt gebruikt. Dit voorkomt dat de ontwikkelaar verrast wordt door de class types. Als u toch zelf een property in uw applicatie wilt toevoegen, PropGuard kan globaal met PropGuard::$armed = false worden uitgeschakeld.
- TokenGenerator
- een class die zorgt voor een gegevensstroom via een generator interface, in plaats van via een gebeurtenissenstroom. Het maakt een eigen Tokenizer aan. Het verwerken van gebeurtenissen op deze manier is minder efficiënt, maar het kan in sommige gevallen gemakkelijker zijn.
Debuggen is ook mogelijk:
- DispatchTracer
- Deze class staat tussen Tokenizer en Dispatcher. Het rapporteert alle token gebeurtenissen en de insertie mode transities binnen Dispatcher. De logberichten worden met een callback functie verzonden.
- TreeMutationTracer
- Dit stuurt gebeurtenissen door over wijzigingen aan de boom afkomstig van TreeBuilder, en meldt de als een callback.
- DestructTracer
- Deze class stuurt gebeurtenissen door waarbij de boomstructuur wordt gewijzigd. Het meldt ze als het object Element door de TreeBuilder gemaakt, wordt verwijderd. Hiermee kunnen geheugenlekken worden gevonden.
Het model van RemexHtml met configureerbare 'pipeline' zorgt voor veel flexibiliteit. Applicaties kunnen subclasses hiervan maken die al aangeboden worden door RemexHtml of hun eigen versie maken, dit om de relevante interface voor de ontvanger te implementeren. Of zij kunnen een eigen fase invoegen tussen de standaardfases (stages) van RemexHtml.
Voor de eenvoudige situaties is een boilerplate wel afdoende. Er is een voorstel T217850 om een eenvoudige methode voor het maken van een standaard pipeline te maken, dit is echter nog niet geïmplementeerd.
Voorbeelden
De DOM opbouwen uit invoertekst
use RemexHtml\DOM\DOMBuilder;
use RemexHtml\TreeBuilder\TreeBuilder;
use RemexHtml\TreeBuilder\Dispatcher;
use RemexHtml\Tokenizer\Tokenizer;
function parseHtmlToDom( $input ) {
$domBuilder = new DOMBuilder();
$treeBuilder = new TreeBuilder( $domBuilder );
$dispatcher = new Dispatcher( $treeBuilder );
$tokenizer = new Tokenizer( $dispatcher, $input );
$tokenizer->execute();
return $domBuilder->getFragment();
}
In het bovenstaande stukje code wordt de 'pipeline' achteraf aangemaakt, van einde naar het begin. De constructor van elke 'pipeline' ontvangt de volgende 'pipeline'. Als het volledig is aangemaakt zorgt $tokenizer->execute() ervoor dat de hele invoertekst door de parser wordt verwerkt verzonden door de pijplijn, waarbij eventueel de DOMBuilder wordt bereikt. Na uitvoering is het aangemaakte document beschikbaar via $domBuilder->getFragment().
Doelen link wijzigen
use RemexHtml\HTMLData;
use RemexHtml\Serializer\HtmlFormatter;
use RemexHtml\Serializer\Serializer;
use RemexHtml\Serializer\SerializerNode;
use RemexHtml\Tokenizer\Tokenizer;
use RemexHtml\TreeBuilder\Dispatcher;
use RemexHtml\TreeBuilder\TreeBuilder;
function changeLinks( $html ) {
$formatter = new class extends HtmlFormatter {
public function element( SerializerNode $parent, SerializerNode $node, $contents ) {
if ( $node->namespace === HTMLData::NS_HTML
&& $node->name === 'a'
&& isset( $node->attrs['href'] )
) {
$node = clone $node;
$node->attrs = clone $node->attrs;
$node->attrs['href'] = 'http://example.com/' . $node->attrs['href'];
}
return parent::element( $parent, $node, $contents );
}
};
$serializer = new Serializer( $formatter );
$treeBuilder = new TreeBuilder( $serializer );
$dispatcher = new Dispatcher( $treeBuilder );
$tokenizer = new Tokenizer( $dispatcher, $html );
$tokenizer->execute();
return $serializer->getResult();
}
In dit voorbeeld wordt een HTML-document aangepast, waarbij de href attributen binnen <a>
tags worden gewijzigd en er een HTML-string wordt teruggegeven.
Dit gaat via het aanmaken van subclassen van HtmlFormatter, wat een relatief eenvoudige hook is voor 'reserialization'.
Het kloont de objecten SerializerNode en Attributes om te voorkomen dat bij het wijzigen van het document zoals gezien bij Serializer, het mogelijk is dat de functie vaker dan een keer per node wordt aangeroepen en het niet de bedoeling is de domeinnaam meer dan een prefix krijgt.
Een andere manier zou zijn om SerializerNode::$snData als een vlag te zien, om daarmee dubbele prefixen te voorkomen:
if ( !$node->snData ) {
$node->snData = true;
$node->attrs['href'] = 'http://example.com/' . $node->attrs['href'];
}
Performance
Er zijn opties om de performance te verbeteren, maar dat kan ten koste gaan van de kwaliteit:
- Tokenizer
- ignoreErrors - Dit is net het eenvoudig weglaten van fouten bij het parsen als die worden gegenereerd. In enkele gevallen kiest het een meer efficiënt algoritme die impliciet fouten negeert. Als parser fouten niet vereist zijn, dan moet dit worden gezet.
- skipPreprocess - De HTML-specificatie vereist dat de invoer vooraf wordt verwerkt om regelovergangen te normaliseren en controle-tekens te verwijderen. Als de regelovergangen al genormaliseerd zijn door de applicatie en u het niet erg vindt dat en controle-tekens worden doorgegeven in de uitvoer, dan kan deze optie worden ingeschakeld, de performance wordt dan ietsjes beter.
- ignoreNulls - Door het inschakelen van deze optie worden eventuele null tekens aan de uitvoer doorgegeven. De HTML-specificatie vereist een complexe, context afhankelijke behandeling van null tekens die in de invoer voorkomen. Door de null tekens eenvoudig weg te laten, wat deze optie doet, dan wordt de uitvoer niet conform de standaarden, de performance wordt wel ietsjes beter.
- ignoreCharRefs - Dit is een zelden bruikbare optimalisatie optie die tekenreferenties negeert, door ze ongewijzigd door te geven. Het moet worden gekoppeld met een speciale serializer dat ampersands doorgeeft in plaats van ze te escapen.
- TreeBuilder
- ignoreNulls, ignoreErrors - Gelijk aan de overeenkomende opties bij de Tokenizer
Over TokenizerError exceptions
Als RemexHtml een exception TokenizerError maakt, bijvoorbeeld "pcre.backtrack_limit exhausted", dan is dat meestal geen fout RemexHtml. Dan moet de relevante configuratie-instelling worden verhoogd of in de invoergrootte moet worden verlaagd. De INI instelling pcre.backtrack_limit moet minstens twee keer de invoergrootte zijn.
Zie ook
- T89331 – Tidy in MediaWiki parser vervangen door HTML5 parse/reserialize
- Html5Depurate (eerdere optie)
- Parsing/Vervanging Tidy