Archive for the 'css' Category

Implémentation d’un modèle CSS

J’ai presque terminé de développer mon modèle CSS. Il me manque néanmoins deux parties importantes, la distinction entre les différents type de valeurs : valeurs spécifiées, calculées et réelles, ainsi que le traitement des différents type de sélecteurs.

En revanche, l’héritage et la cascade sont entièrement implémentées, il ne me reste plus qu’à terminer quelques tests unitaires. C’est la classe CSS2EngineImpl qui a la responsabilité de la cascade et de l’héritage. Elle collabore avec les classes StyleRule.SelectorMessenger et Value pour le calcul de la spécificité des sélecteurs et de la prévalence des valeurs dans l’ordre de la cascade.

La réalisation des tests d’intégration prendra plus de temps car il me faut encore raccorder le parser CSS avec le modèle CSS. Je pense que je me contenterai d’une implémentation élémentaire du DOM en ne définissant que les méthodes qui sont effectivement appellées.

Enfin, la partie qui s’annonce la plus longue est la plus rébarbative est le support des divers propriétés CSS. Je me contenterai dans un premier temps des propriétés basiques comme la couleur, les dimensions et la position des composants. J’ajouterai une propriété non standard : layout, puisque c’est pour cette raison que j’ai implémenté le support de CSS.

Lorsque j’aurai terminé ces différents points, je publierai la version 0.5, si tout se déroule bien dans le courant du mois de juin.

Choix du modèle CSS

Cet article fait suite aux articles précédent :

J’ai finalement décidé de ne pas utiliser le moteur de CSS de batik. Plusieurs raisons m’ont amené à cette décision. Tout d’abord, batik n’implémente pas l’héritage, mais seulement la cascade, il revient donc au client de le faire. Mais ce qui m’a le plus dérangé, c’est le caractère obscure de l’api. Cette librairie est très difficile à comprendre, mal documentée pour la partie CSS et je dirais même mal conçue.

Un exemple concret est que le constructeur de CSSEngine prend 13 valeurs en argument. Oui vous avez bien lu, treize !!! Inévitablement, il est difficile de comprendre le rôle de chacun de ces arguments.

Il existe par ailleurs ce qui me semble être des incohérences. Par exemple, lorsqu’on appelle la méthode getCascadedStyleMap(CSSStylableElement,String), celle-ci cherche à récupérer les valeurs actuelles des attributs que l’on veut justement initialiser. Sans doute y a-t-il une logique dans ce mécanisme, mais elle m’échappe. Le seul cas de figure où il serait nécessaire d’y accéder serait le support des sélecteurs d’attributs, mais ce n’est pas le cas ici. Autre étrangeté, cette méthode, qui est censée être appelée après avoir parsé la feuille de style, accède à nouveau au parser CSS. Cela signifie que le modèle de la CSS est incomplet ou incohérent.

En outre, certains indices me laissent penser que les performances du moteur de style doivent être désastreuses.

Pour toutes ces raisons, je préfère implémenter moi-même le moteur de CSS. Je me baserai sur la spécification DOM-Level-2-Style pour la définition du modèle CSS. Néanmoins, je ne respecterai pas l’implémentation de cette spécification (org.w3c.dom.css) au pied de la lettre, car là encore, j’ai relevé des incohérences. La spécification précise clairement que le modèle ne doit pas donner accès aux valeurs dites « spécifiée » ou « réelle », mais seulement aux valeurs « calculée ». Ce qui signifie que les instances de CSSPrimitiveValue sont des valeurs calculées puisqu’elles sont obtenues à partir du modèle. Or cette même spécification précise que les valeurs calculées doivent être accessibles en mode « read-only » tandis que l’interface CSSPrimitiveValue expose des setters.

Le moteur de style sera applicable à tout modèle XML. J’ai donc dors et déjà commencé à implémenter un modèle XML standard. Mais je me suis lancé dans une approche expérimentale. En effet, l’api swing implémente déjà un modèle qui repose sur le pattern Composite. Implémenter un modèle parallèle obligerait donc à synchroniser ces deux modèles avec toutes les difficultés que cela entraîne. Sans compter les problèmes de fuite de mémoire.

Pour résoudre ces inconvénients, j’ai défini un pattern que j’appellerai Messenger.

Messenger est un croisement des patterns Adapter et Singleton. Il consiste à présenter au client l’interface qu’il attend, comme dans le cas d’Adapter, mais cette interface est implémentée par un Singleton, ce qui évite les fuites de mémoire et évacue les problèmes de synchronisation. L’inconvénient majeur de ce pattern est qu’il doit être utilisé avec beaucoup de précaution. En particulier, les instances de Messenger ne doivent être référencées que par des variables dont la portée est strictement limitée à la méthode qui les manipule. Si on a besoin de conserver une référence, celle-ci doit pointer sur la valeur véhiculée par le Messenger, et non sur le Messenger lui-même. En outre, Messenger n’est pas adapté à un context multi-thread.

Pour l’instant, je souhaite enrichir fonctionnellement le framework Swinger, mais je consacrerai une release à l’optimisation des performances. Le pattern Messenger offre un potentiel de gain assez important en particulier au niveau des parsers CSS et XML.

Il me reste dorénavant à implémenter le modèle de CSS ainsi que l’algorithme de la cascade et de l’héritage. Pour cela, il me faudra étudier en détail la spécification CSS2. Je vous ferai part de mes réflexions sur ce sujet dans un prochain billet.

Le choix de mon parser CSS

J’ai décidé d’implémenter les CSS dans mon projet swinger. Le W3C propose une interface (SAC) écrite en Java qui est implémentée par deux API, Flute et Batik.

Flute est une implémentation basique de SAC, il s’agit d’un simple parser, tandis que Batik est un projet beaucoup plus vaste qui est en fait un toolkit pour le support du format SVG.

Il n’existe que très peu de documentation sur ces deux parsers, j’ai donc commencé par étudier le code source de ces deux API. Le parser de Flute repose sur un ParserTokenManager qui a la responsabilité d’extraire les éléments gramaticaux du fichier CSS. Dans l’api Batik, ce role est dévolu à la classe Scanner. Dans les deux cas, le parser est associé à un DocumentHandler qui reçoit les évènements qui surviennent lors du parcours du fichier CSS.

J’ai effectué des tests de performance pour comparer les deux parsers, et il ressort que le parser de Batik est 40 à 50% plus rapide que celui de Flute. Cette différence ne me surprend pas vraiment car le parser de Flute a été généré avec JavaCC. Il s’agit d’un outil extrêmement puissant mais qui a l’inconvénient de produire un code absolument dégueulasse.

J’ai également comparé les évènements produits par les deux parsers. Et là je suis tombé sur un os. Toute la puissance du langage CSS repose sur les sélecteurs. Tant que l’on utilise des sélecteurs élémentaires telles que des id, des classes ou des élements xml, il n’y a aucun problème. Mais dès que l’on introduit un peu de complexité en jouant sur les opérateurs, on constate des différences d’interprétation. Je ne maîtrise pas suffisament les CSS pour savoir lequel des parsers est en faute.

Il s’agit d’un problème assez sérieux, mais pas rédhibitoire. La nécessité de standardiser les usages du web vient du fait qu’il existe une grande diversité de navigateurs. Firefox, Opera, Safari, Chrome, Internet Explorer, pour ne citer que les plus connus. Dans mon cas, la nécessité de respecter les standards n’est dicté que par le soucis de bien faire. Néanmoins, compte tenu de cette différence d’interprétation, il est évident qu’il sera nécessaire de modifier le code source et qu’il ne sera pas possible de considérer le parser comme une simple boîte noire.

Cette dernière considération me fait choisir le parser Batik, car l’utilisation de JavaCC dans le cas de Flute me parait rendre l’analyse et la correction de bugs trop complexe, sans compter qu’il n’est pas totalement exclu que JavaCC soit lui-même à l’origine de cette différence d’interprétation.

Maintenant que le choix de mon parser est fait, je dois maintenant décider jusqu’à quel point je souhaite utiliser la librairie Batik. En effet, Batik propose un modèle de CSS complet accompagné d’un moteur qui implémente l’héritage et la cascade. Mais cela reste à vérifier. Je vous ferai partager mes réflexions à ce sujet lors d’un projet billet.

Une interview de Daniel Glazman

Je ne suis pas un spécialiste des CSS, mais le site Open Web nous propose une interview intéressante de l’un des acteurs les plus influents du W3C WG qui édite les spécifications du W3C (via le standblog).