Convertir des styles inlines en feuille de style CSS avec un service Symfony
Prérequis
Pour bien suivre ce tutoriel, vous devez être à l’aise avec Symfony. Le code du tutoriel à réaliser avec Symfony 7 et PHP 8.2.
Quel intérêt de convertir des styles inlines en feuille de style CSS ?
Récemment, je me suis retrouvé à devoir créer un blog qui était une nouvelle version d’un blog plus ancien. Pour migrer l’ensemble des articles de l’ancien blog vers le nouveau, j’ai fait un peu de web scrapping. Mais j’ai rencontré un problème, tous les articles ne comportait que des styles inlines, pas une seule classe à l’horizon.
Pourquoi les styles inlines sont un problème ?
Les styles inlines posent un problème de sécurité, car ils exposent votre site internet à une faille XSS. Ce type de faille pourrait permettre à un attaquant d’injecter des styles dans votre ce qui pourrait altérer son bon fonctionnement.
Pour se prémunir de ce type de faille, le meilleur moyen est de mettre en place une Content Security Policy (CSP) pour définir de quel source peuvent provenir les codes autorisés à s’exécuter sur votre site web. Il y est par ailleurs vivement conseillé d’interdire l’exécution des styles CSS inlines.
Pour plus d’information, vous pouvez vous rendre sur cette page https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP (en anglais). Vous pouvez également tester la mise en place des bon headers sur votre serveur concernant la sécurité incluant la CSP sur cette page : https://developer.mozilla.org/en-US/observatory .
Dans quel cas le service que nous allons créer sera utile ?
Si vous avez un contrôle direct sur le code html, le service que nous allons créer ne vous sera pas utile.
Mais il peut exister des cas, où cela n’est pas possible, typiquement dans mon cas, j’ai dû gérer plusieurs centaines d’articles à importer dont je devais conserver le style, il aurait été impensable de faire ce changement manuellement. Cela peut également vous être utile si vous avez un back-office comportant un vieil éditeur WYSIWYG qui utilise des styles inlines pour la mise en page.
Créer un service pour convertir les styles inlines en feuille de style CSS
Préparation
Pour que vous puissiez tester le bon fonctionnement du service, je vous propose d’utiliser le texte suivant créé par ChatGPT avec des textes inlines qui servira de référence pour la suite.
<h1 style="font-size: 24px; font-weight: bold; color: #2c3e50; margin-bottom: 10px;">Les Nouveautés de la Technologie en 2024</h1>
<p style="margin: 0 0 15px; line-height: 1.8;">
La <strong style="font-weight: bold; color: #e74c3c;">technologie</strong> évolue à une vitesse fulgurante en 2024. Avec des innovations dans des secteurs tels que l'<em style="font-style: italic;">intelligence artificielle</em> (IA),
la réalité augmentée (RA) et la 5G, les entreprises continuent de transformer notre quotidien. Ces progrès apportent des <span style="background-color: #f9e79f;">opportunités</span> immenses, mais également des défis importants.
</p>
<h2 style="font-size: 20px; font-weight: bold; color: #3498db; margin-bottom: 10px;">L'Intelligence Artificielle</h2>
<p style="margin: 0 0 15px; line-height: 1.8;">
L'IA devient plus présente dans divers secteurs, y compris la <u style="text-decoration: underline;">santé</u>, l'éducation et l'industrie. Grâce à des algorithmes avancés, les machines peuvent maintenant apprendre et
s'adapter à des scénarios complexes, dépassant les capacités humaines dans certaines tâches spécifiques. Toutefois, cela soulève des préoccupations éthiques, notamment autour de la confidentialité et de l'emploi.
</p>
<blockquote style="margin: 15px 0; padding: 10px; background-color: #ecf0f1; border-left: 5px solid #2980b9;">
"L'IA ne remplacera pas les humains, mais ceux qui l'utilisent remplaceront ceux qui ne le font pas." — Anonyme
</blockquote>
<h3 style="font-size: 18px; font-weight: bold; color: #2ecc71; margin-bottom: 10px;">Les Applications de la Réalité Augmentée</h3>
<p style="margin: 0 0 15px; line-height: 1.8;">
La <strong style="color: #8e44ad;">réalité augmentée</strong> (RA) permet d'intégrer des éléments virtuels dans le monde réel via des dispositifs comme des smartphones ou des lunettes intelligentes.
En 2024, les entreprises investissent massivement dans cette technologie pour des applications pratiques, allant de la formation professionnelle à la visualisation d'objets en 3D avant achat.
</p>
<ul style="margin: 0 0 15px; padding-left: 20px;">
<li style="margin-bottom: 5px;">Applications dans l'e-commerce</li>
<li style="margin-bottom: 5px;">Amélioration de la formation</li>
<li style="margin-bottom: 5px;">Expériences immersives dans le jeu vidéo</li>
</ul>
<h2 style="font-size: 20px; font-weight: bold; color: #e67e22; margin-bottom: 10px;">La Montée en Puissance de la 5G</h2>
<p style="margin: 0 0 15px; line-height: 1.8;">
Avec le déploiement global de la <strong style="font-weight: bold;">5G</strong>, la connectivité mobile a atteint un nouveau niveau. Les vitesses de téléchargement et d'upload sont largement améliorées, et
cette technologie ouvre la voie à de nouvelles innovations, notamment dans les domaines de l'<em style="font-style: italic;">Internet des objets</em> (IoT), des véhicules autonomes, et de la réalité virtuelle.
</p>
<h3 style="font-size: 18px; font-weight: bold; color: #d35400; margin-bottom: 10px;">Les Défis Restants</h3>
<p style="margin: 0 0 15px; line-height: 1.8;">
Malgré les promesses de la 5G, certains défis subsistent. Les infrastructures nécessaires sont coûteuses, et il existe des préoccupations quant aux effets sur la santé et l'environnement, bien que
les recherches à ce sujet soient encore en cours.
</p>
<footer style="font-size: 12px; color: #7f8c8d; margin-top: 20px;">
<p style="margin: 0;">Publié par <strong style="font-weight: bold;">TechNews</strong> | © 2024</p>
</footer>
Vous pouvez stocker ce texte dans un fichier ou une base de données, il faudra simplement le passé en paramètre à notre service pour pouvoir le tester.
Pour faire fonctionner notre service, nous aurons besoin d’ajouter, ce package https://github.com/wasinger/htmlpagedom avec composer, il nous permettra de faciliter la manipulation du code html pour y faire les modifications nécessaires.
composer require wa72/htmlpagedom
Passons au code
Pour commencer, nous allons créer la classe InlineToCssClassConverter dans un dossier Service du répertoire src.
Cette classe comportera une méthode public convert qui prendra en paramètre le texte original et renverra un tableau constitué de deux valeurs : le nouveau texte avec les classes et un tableau des classes créées de ce type [‘font-size-20px’ => ‘font-size: 20px’].
<?php
namespace App\Service;
use Wa72\HtmlPageDom\HtmlPageCrawler;
class InlineToCssClassConverter
{
public function convert(string $text): array
{
}
}
Dans cette méthode, nous allons d’abord, créer un objet HtmlPageCrawler et lui passer en paramètre le texte original, cet objet nous permettra de mieux manipuler le code html via un dom. Nous allons également le tableau $inlineClasses dans lequel on ajoutera par la suite les classes.
<?php
namespace App\Service;
use Wa72\HtmlPageDom\HtmlPageCrawler;
class InlineToCssClassConverter
{
public function convert(string $text): array
{
$textDom = new HtmlPageCrawler($text);
$inlineClasses = [];
// On ajoutera le reste du code de la fonction ici
return [$textDom, $inlineClasses];
}
}
Nous allons ensuite filtrer les éléments ayant un attribut style puis itérer sur ces derniers pour appliquer les modifications.
<?php
namespace App\Service;
use Wa72\HtmlPageDom\HtmlPageCrawler;
class InlineToCssClassConverter
{
public function convert(string $text): array
{
$textDom = new HtmlPageCrawler($text);
$inlineClasses = [];
$textDom->filter('[style]')->each(function (HtmlPageCrawler $element) use (&$inlineClasses){
});
return [$textDom, $inlineClasses];
}
}
Pour extraire les styles de chacun des éléments du dom nous allons créer la méthode privée extractInlineStylesFromElement qui retournera les styles d’un élément sous la forme d’un tableau.
<?php
namespace App\Service;
use Wa72\HtmlPageDom\HtmlPageCrawler;
class InlineToCssClassConverter
{
//...
private function extractInlineStylesFromElement(HtmlPageCrawler $element): array
{
$elementStyle = explode(';', $element->getAttribute('style'));
$elementStyle = array_filter($elementStyle, function ($style) {
return $style != '';
});
$elementStyle = array_map(function ($style) {
return trim($style);
}, $elementStyle);
return $elementStyle;
}
}
Nous allons ensuite implémenter cette méthode dans la méthode convert et itéré sur le tableau de style qu’elle retourne.
<?php
namespace App\Service;
use Wa72\HtmlPageDom\HtmlPageCrawler;
class InlineToCssClassConverter
{
public function convert(string $text): array
{
$textDom = new HtmlPageCrawler($text);
$inlineClasses = [];
$textDom->filter('[style]')->each(function (HtmlPageCrawler $element) use (&$inlineClasses){
$elementStyle = $this->extractInlineStylesFromElement($element);
$elementClass = array_map(function ($style) use (&$inlineClasses) {
}, $elementStyle);
});
return [$textDom, $inlineClasses];
}
//...
}
Nous avons maintenant besoin de créer les classes correspondant à chaque style pour ce faire nous allons créer la méthode privée convertInlineToClass qui créera le nom de la classe à créer à partir de la règle de style correspondante.
<?php
namespace App\Service;
use Wa72\HtmlPageDom\HtmlPageCrawler;
class InlineToCssClassConverter
{
//...
private function convertInlineToClass(string $style): string
{
$styleWithoutSpace = str_replace(': ', '-', $style);
$styleWithoutSpace = str_replace(' ', '-', $styleWithoutSpace);
$keyClass = str_replace(
[
'!',
'"',
'#',
'$',
'%',
'&',
"'",
'(',
')',
'*',
'+',
',',
'.',
'/',
':',
';',
'<',
'=',
'>',
'?',
'@',
'[',
'\\',
']',
'^',
' ',
'{',
'|',
'}',
'~',
],
'',
$styleWithoutSpace
);
return $keyClass;
}
}
Nous allons ensuite utiliser cette classe dans la méthode convert pour remplir le tableau $inlineClasses en ajoutant pour chaque nouvelle entrée la nouvelle classe en clé et le style original en valeur.
<?php
namespace App\Service;
use Wa72\HtmlPageDom\HtmlPageCrawler;
class InlineToCssClassConverter
{
public function convert(string $text): array
{
$textDom = new HtmlPageCrawler($text);
$inlineClasses = [];
$textDom->filter('[style]')->each(function (HtmlPageCrawler $element) use (&$inlineClasses){
$elementStyle = $this->extractInlineStylesFromElement($element);
$elementClass = array_map(function ($style) use (&$inlineClasses) {
$keyClass = $this->convertInlineToClass($style);
$inlineClasses[$keyClass] = $style;
return $keyClass;
}, $elementStyle);
});
return [$textDom, $inlineClasses];
}
//...
}
Enfin, nous devons modifier chaque élément pour retirer les styles inlines et y ajouter les nouvelles classes, pour ce faire nous allons créer la méthode privée editElement.
<?php
namespace App\Service;
use Wa72\HtmlPageDom\HtmlPageCrawler;
class InlineToCssClassConverter
{
//...
private function editElement(HtmlPageCrawler $element, array $elementClass): void
{
$element->removeAttr('style');
$existingClass = $element->getAttribute('class');
$elementClass = array_merge($existingClass ? explode(' ', $existingClass) : [], $elementClass);
$element->setAttribute('class', implode(' ', $elementClass));
}
}
Nous allons finalement l’ajouter à notre méthode convert.
<?php
namespace App\Service;
use Wa72\HtmlPageDom\HtmlPageCrawler;
class InlineToCssClassConverter
{
//...
public function editElement(HtmlPageCrawler $element, array $elementClass): void
{
$element->removeAttr('style');
$existingClass = $element->getAttribute('class');
$elementClass = array_merge($existingClass ? explode(' ', $existingClass) : [], $elementClass);
$element->setAttribute('class', implode(' ', $elementClass));
}
}
Notre classe InlineToCssClassConverter est maintenant terminée.
Créer la feuille de style CSS
Pour créer la feuille CSS à partir du tableau de classes retourné par la fonction convert de notre classe InlineToCssClassConverter, nous pouvons créer la classe CssStyleSheetGenerator qui créera une feuille de style CSS et l’enregistrera où vous le souhaitez.
<?php
namespace App\Service;
class CssStyleSheetGenerator
{
public function saveCssSheets(array $inlineClasses, string $path): void
{
$sheetContent = $this->generateCssSheets($inlineClasses);
$this->saveFiles($path, $sheetContent);
}
private function generateCssSheets(array $inlineClasses): string
{
$result = '';
$inlineCss = array_unique($inlineClasses);
foreach ($inlineCss as $class => $css) {
$result .= '.' . $class . ' {' . $css . ';}' . PHP_EOL;
}
return $result;
}
private function saveFiles(string $path, string $content): void
{
file_put_contents($path, $content);
}
}
Normalement, dans notre cas, cette classe devrait nous permettre de créer une feuille de style ayant le contenu suivant.
.font-size-24px {font-size: 24px;}
.font-weight-bold {font-weight: bold;}
.color-2c3e50 {color: #2c3e50;}
.margin-bottom-10px {margin-bottom: 10px;}
.margin-0-0-15px {margin: 0 0 15px;}
.line-height-18 {line-height: 1.8;}
.color-e74c3c {color: #e74c3c;}
.font-style-italic {font-style: italic;}
.background-color-f9e79f {background-color: #f9e79f;}
.font-size-20px {font-size: 20px;}
.color-3498db {color: #3498db;}
.text-decoration-underline {text-decoration: underline;}
.margin-15px-0 {margin: 15px 0;}
.padding-10px {padding: 10px;}
.background-color-ecf0f1 {background-color: #ecf0f1;}
.border-left-5px-solid-2980b9 {border-left: 5px solid #2980b9;}
.font-size-18px {font-size: 18px;}
.color-2ecc71 {color: #2ecc71;}
.color-8e44ad {color: #8e44ad;}
.padding-left-20px {padding-left: 20px;}
.margin-bottom-5px {margin-bottom: 5px;}
.color-e67e22 {color: #e67e22;}
.color-d35400 {color: #d35400;}
.font-size-12px {font-size: 12px;}
.color-7f8c8d {color: #7f8c8d;}
.margin-top-20px {margin-top: 20px;}
.margin-0 {margin: 0;}
Vous pourrez alors intégrer ces styles dans la template ou vous afficherez le code html créé par la classe InlineToCssClassConverter qui devrait normalement être comme ci-dessous :
<h1 class="font-size-24px font-weight-bold color-2c3e50 margin-bottom-10px">Les Nouveautés de la Technologie en 2024</h1>
<p class="margin-0-0-15px line-height-18">
La <strong class="font-weight-bold color-e74c3c">technologie</strong> évolue à une vitesse fulgurante en 2024. Avec des innovations dans des secteurs tels que l'<em class="font-style-italic">intelligence artificielle</em> (IA),
la réalité augmentée (RA) et la 5G, les entreprises continuent de transformer notre quotidien. Ces progrès apportent des <span class="background-color-f9e79f">opportunités</span> immenses, mais également des défis importants.
</p>
<h2 class="font-size-20px font-weight-bold color-3498db margin-bottom-10px">L'Intelligence Artificielle</h2>
<p class="margin-0-0-15px line-height-18">
L'IA devient plus présente dans divers secteurs, y compris la <u class="text-decoration-underline">santé</u>, l'éducation et l'industrie. Grâce à des algorithmes avancés, les machines peuvent maintenant apprendre et
s'adapter à des scénarios complexes, dépassant les capacités humaines dans certaines tâches spécifiques. Toutefois, cela soulève des préoccupations éthiques, notamment autour de la confidentialité et de l'emploi.
</p>
<blockquote class="margin-15px-0 padding-10px background-color-ecf0f1 border-left-5px-solid-2980b9">
"L'IA ne remplacera pas les humains, mais ceux qui l'utilisent remplaceront ceux qui ne le font pas." — Anonyme
</blockquote>
<h3 class="font-size-18px font-weight-bold color-2ecc71 margin-bottom-10px">Les Applications de la Réalité Augmentée</h3>
<p class="margin-0-0-15px line-height-18">
La <strong class="color-8e44ad">réalité augmentée</strong> (RA) permet d'intégrer des éléments virtuels dans le monde réel via des dispositifs comme des smartphones ou des lunettes intelligentes.
En 2024, les entreprises investissent massivement dans cette technologie pour des applications pratiques, allant de la formation professionnelle à la visualisation d'objets en 3D avant achat.
</p>
<ul class="margin-0-0-15px padding-left-20px">
<li class="margin-bottom-5px">Applications dans l'e-commerce</li>
<li class="margin-bottom-5px">Amélioration de la formation</li>
<li class="margin-bottom-5px">Expériences immersives dans le jeu vidéo</li>
</ul>
<h2 class="font-size-20px font-weight-bold color-e67e22 margin-bottom-10px">La Montée en Puissance de la 5G</h2>
<p class="margin-0-0-15px line-height-18">
Avec le déploiement global de la <strong class="font-weight-bold">5G</strong>, la connectivité mobile a atteint un nouveau niveau. Les vitesses de téléchargement et d'upload sont largement améliorées, et
cette technologie ouvre la voie à de nouvelles innovations, notamment dans les domaines de l'<em class="font-style-italic">Internet des objets</em> (IoT), des véhicules autonomes, et de la réalité virtuelle.
</p>
<h3 class="font-size-18px font-weight-bold color-d35400 margin-bottom-10px">Les Défis Restants</h3>
<p class="margin-0-0-15px line-height-18">
Malgré les promesses de la 5G, certains défis subsistent. Les infrastructures nécessaires sont coûteuses, et il existe des préoccupations quant aux effets sur la santé et l'environnement, bien que
les recherches à ce sujet soient encore en cours.
</p>
<footer class="font-size-12px color-7f8c8d margin-top-20px">
<p class="margin-0">Publié par <strong class="font-weight-bold">TechNews</strong> | © 2024</p>
</footer>
Voilà, vous disposez maintenant d’un service vous permettant de remplacer automatiquement les styles inlines par des classes CSS dans vos codes html.