Bewerken van HTML met NekoHTML

Inleiding

NekoHTML is een HTML parser die HTML omzet in XML zodat deze bewerkt kan worden met standaard XML interfaces, zoals SAX of DOM. Tags en attributen kunnen bijvoorbeeld dynamisch worden verwijderd, toegevoegd of aangepast. Dit kan natuurlijk ook met reguliere expressies maar dit kan al snel erg ingewikkeld worden. Aan de hand van een probleem uit de praktijk wordt een eenvoudige toepassing van Neko getoond waarbij hyperlinks in een HTML document worden aangepast.

Voorbeeld

Dit voorbeeld komt uit een situatie waarbij HTML data uit een applicatie moest worden geïmporteerd naar een andere. De bron applicatie gebruikte zelf gedefinieerde attributen, die tijdens het importeren moesten worden omgezet naar een generiek formaat. Het volgende HTML fragment geeft daarvan een vereenvoudigd voorbeeld:

<p>
<a href="abc.html" customId="123">mijn url</a>
</p>

Een anchor tag met een statische URL en een applicatie specifiek attribuut. Deze URL moet worden veranderd in een dynamische URL met het applicatie specifieke attribuut als parameter:

<p>
<a href="abc.jsp?id=123">mijn url</a>
</p>

Het volgende stukje code laat zien hoe een HTML document met Neko is om te zetten naar een XML document:

    InputSource source = ... // bron, bijvoorbeeld een bestand
    DOMFragmentParser parser = new DOMFragmentParser();
    HTMLDocument document = new HTMLDocumentImpl();
    DocumentFragment fragment = document.createDocumentFragment();
    parser.parse(source, fragment);

In dit geval wordt hier de DOMFragmentParser gebruikt, deze is geschikt om HTML fragmenten in te lezen. Neko gebruikt een andere parser voor volledige HTML documenten. De InputSource geeft de HTML bron aan, bijvoorbeeld een bestand. In het HTMLDocument wordt het resultaat opgeslagen, dat bewerkt kan worden als een XML document. Om het document te bewerken gebruiken we een SAX filter. SAX filters kunnen bewerkingen uitvoeren op een XML document terwijl dit gelezen wordt. Het principe van SAX filters is niet specifiek voor Neko maar kan op elk XML document worden toegepast, zie de lijst met bronnen voor meer informatie hierover. Neko gebruikt Xerces als implementatie voor deze filters. Deze zijn iets gemakkelijker in het gebruik dan de standaard SAX filters. De filters koppelen we aan de parser door een property te zetten:

     XMLDocumentFilter filter = new HyperlinkFilter();
     XMLDocumentFilter writer = new Writer();
     XMLDocumentFilter[] filters = { filter, writer };

     DOMFragmentParser parser = new DOMFragmentParser();
     parser.setProperty("http://cyberneko.org/html/properties/filters", filters);
     parser.setProperty("http://cyberneko.org/html/properties/names/elems", "match");
     parser.setProperty("http://cyberneko.org/html/properties/names/attrs", "match");

Het Writer filter wordt gebruikt om het resultaat naar een OutputStream te schrijven, in dit geval System.out. We zijn daarom ook niet geinteresseerd in het gegenereerde HTMLDocument maar we kunnen deze Outputstream gebruiken. De andere properties worden gebruikt om aan te geven of Neko wel of niet hoofdletter gevoelig is.

Het filter dat we gebruiken om de hyperlinks aan te passen is eenvoudig te maken door het Neko DefaultFilter uit te breiden. We hoeven vervolgens alleen de methode startElement te overriden. Hier worden de relevante tags gefilterd en indien nodig aangepast:

public class HyperlinkFilter extends DefaultFilter {
	@Override
    	public void startElement(QName element, XMLAttributes attributes, Augmentations augs)
	throws XNIException {
        	if ("a".equalsIgnoreCase((element.localpart))) {
            		int indexCustomId = attributes.getIndex("customId");
            		int indexHref = attributes.getIndex("href");
            		if (indexCustomId >= 0 && indexHref >= 0) {
               			String href = attributes.getValue(indexHref);
               			String customId = attributes.getValue(indexCustomId);
               			String newHref = href.replaceFirst(".html", ".jsp") + "?id=" + customId;
               			attributes.setValue(indexHref, newHref);
               			attributes.removeAttributeAt(indexCustomId);
            		}
        	}
        	super.startElement(element, attributes, augs);
    	}
}

Dat is alles wat er nodig is om HTML tags te kunnen manipuleren. Neko kan er ook voor zorgen dat tags uitgebalanceerd zijn, bepaalde tags verwijderen of juist alleen bepaalde tags toestaan. Meer informatie is te vinden op website van NekoHTML. De volledige broncode van het voorbeeld is in de lijst met bronnen aangegeven.

Conclusie

Dit voorbeeld laat zien dat HTML documenten gemakkelijk kunnen worden aangepast met NekoHTML.

Bronnen

Werken bij Finalist als Java/JEE developer?


9 reacties »

  1. Ja NekoHTML is erg handig! Bij de VPRO gebruik ik het om alleen een set van gewenste html elementen toe te staan in door externe gebruiker ingevoerde (rich) tekst. Een mooie tool die me nog nooit problemen heeft gegeven.

    Peter Maas - mei 13, 2008 20:25

  2. Groot nadeel van het gebruik van XML als tussen formaat is dat het moet voldoen aan, eh… XML. Vandaar dat een parser als Jericho meestal minder problemen geeft voor het bewerken van HTML. Dit is met name zo voor oudere HTML.

    Erik van Oosten - mei 13, 2008 23:03

  3. @Erik
    NekoHTML zou ook moeten om kunnen gaan met niet al te correct HTML, het balanceert zelf tags etc. Maar Jericho kende ik nog niet, is daar wellicht nog beter in. Na een dag worstelen met regex en xpath etc. was ik al blij dat ik NekoHTML had gevonden.

    Rob van de Meulengraaf - mei 14, 2008 11:16

  4. Heb je ook gekeken naar tidy en de java implementatie jTidy?

    jTidy kan niet alleen de html well-formed maken, maar ook valide (b.v. geen geneste p-elementen).

    jTidy is te downloaden van http://jtidy.sourceforge.net/

    Ik zou zelf jtidy (of NekoHTML) + xslt gebruiken, maar het opzetten van xslt voor dit eenvoudige voorbeeld is waarschijnlijk overkill :)

    Lennaert - mei 14, 2008 16:37

  5. NekoHTML heeft een heel scala aan mogelijkheden om HTML op te schonen, in te stellen via properties. De documentatie is misschien een beetje moeilijk te vinden op de site maar het staat wel in de docs in de download.

    Ik heb hier echter een voorbeeld willen laten zien van een probleem dat erg eenvoudig bleek te zijn op te lossen met NekoHTML (of met JTidy o.i.d.) en XML filters. XSLT zou het er niet eenvoudiger op maken in dit geval lijkt me.

    Rob van de Meulengraaf - mei 15, 2008 11:45

  6. Hoe gaat ie om met optionele tags? bijvoorbeeld de html, body en head tags zijn optioneel, terwijl de elementen er wel altijd zijn (er is verschil tussen tags en elementen :))

    Hoe gaat ie om met implicit closing van elementen? denk aan iets als aapnootmies, wat geparsed zou moeten worden als aapnootmies

    Rikkert Koppes - mei 16, 2008 9:41

  7. Hoe gaat ie om met optionele tags? bijvoorbeeld de html, body en head tags zijn optioneel, terwijl de elementen er wel altijd zijn (er is verschil tussen tags en elementen :))

    Hoe gaat ie om met implicit closing van elementen? denk aan iets als <b>aap<i>noot</b>mies</i>, wat geparsed zou moeten worden als <b>aap<i>noot</i></b>mies

    (ai, er wordt niet ge-html escaped in de comment, bovenstaand geeft iig aan hoe het geparsed wordt)

    Rikkert Koppes - mei 16, 2008 9:43

  8. Ik prefereer xslt boven ‘handmatig’ sleutelen aan de dom-boom. En de template is volgens mij minstens zo eenvoudig:

    Maar ik ben een fan van xslt en daarom bevooroordeeld :)

    Lennaert - mei 16, 2008 10:05

  9. Dat wordt geparsed als <b>aap<i&gtnoot</i&gt</b&gt<i&gtmies</i&gt. Dat lijkt me correct.

    Hoe met html, body en head tags wordt omgegaan is ook instelbaar, zo kun je verschillend omgaan met een volledig document of met een fragment.

    Rob van de Meulengraaf - mei 20, 2008 11:07

Reageer

RSS feed for comments on this post · TrackBack URI