IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage PHP Discussion :

DOMDocument : ajouter des styles CSS inline à la volée


Sujet :

Langage PHP

  1. #1
    Invité
    Invité(e)
    Par défaut DOMDocument : ajouter des styles CSS inline à la volée
    Bonjour,

    J'essaie d'utiliser DOMDocument pour parser une chaine HTML, et ajouter "à la volée" des styles CSS inline, en fonction du nom des balises, des classes ou id des balises.
    Je débute avec DOMDocument...

    Voici un exemple de code : test.php
    Code php : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    <?php
    header('Content-type:text/html; charset=UTF-8');	// encodage UTF-8
    error_reporting(E_ALL); 	// en TEST !!
    ?>
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
    	<title>DOMDocument : ajouter des styles CSS inline à la volée</title>
    </head>
    <body>
     
    <?php
    // ---------------------
    // 1- CONTENU HTML à parser
    // ---------------------
    $contentHTML = <<<EOT
    	<article class="artListe" id="article118">
    	
    		<header>
    			<h1 style="padding:10px;background:yellow;">header h1 : Lorem Ipsum</h1>
    		</header>
    
    		<div class="artContenu">
    
    			<figure>
    				<img src="https://www.developpez.net/forums/avatars/256045-jreaux62.gif?dateline=1487190201" alt="jreaux62" />
    			</figure>
    
    			<div>
    
    				<img src="https://www.developpez.net/forums/avatars/256045-jreaux62.gif?dateline=1487190201" alt="jreaux62" />
    
    				<h1>div h1 : Sub carnifex patratis maestitiam multos</h1>
    				<h2>div h2 : Zenonem Attico audiebamus amaret mentitum</h2>
    				<h3>div h3 : Senatores se hortaretur Maximino cum</h3>
    				<h4>div h4 : Non et in quibusque incidissent</h4>
    
    				<p>div p : Validis Arabia hanc castrisque saepe ad contigua est Nabataeis nomine.</p>
    				<p class="pClass1">div p (pClass1) : Validis Arabia hanc castrisque saepe ad contigua est Nabataeis nomine.</p>
    				<p class="pClass1 pClass2">div p (pClass1 pClass2) : Cum neque commentum nec conduntur quoniam hoc munimentum Paleas omne.</p>
    
    			</div>
    
    		</div>
    
    		<footer>
    			<p>footer p : Lorem Ipsum</p>
    		</footer>
    
    	</article>
    EOT;
    // ---------------------
    // 2- STYLES CSS à appliquer :
    // ---------------------
    $tag_css['header']		= array(
    						'h1' => 'font-size:240%;color:orange;',
    						);
    $tag_css['div']			= array(
    						'figure' => 'border:1px solid red;margin:20px;padding:20px;',
    						'img' => 'border:1px solid lightblue;margin:20px;padding:20px;background:lightgreen;',
    						'p' => 'font-size:100%;color:grey;',
    						'h1' => 'font-size:180%;color:green;',
    						'h2' => 'font-size:160%;color:blue;',
    						'h3' => 'font-size:140%;color:orange;',
    						'h4' => 'font-size:120%;color:purple;',
    						);
    $tag_css['footer']		= array(
    						'p' => 'font-size:90%;background:orange;color:#fff;text-align:center;',
    						);
    // ---------------------
    // 3- PARSER avec DOMDocument
    // ---------------------
    $domDocument = new DOMDocument;
    // on évite l'affichage d'erreurs html en les redirigeant vers le gestionnaire d'erreurs de libxml. On sauvegarde l'ancien état dans $state.
    $state = libxml_use_internal_errors(true);
    // on charge le contenu HTML
    $domDocument->loadHTML( $contentHTML, LIBXML_HTML_NOIMPLIED );
    $domDocument->preserveWhiteSpace = false;
    // on restitue l'ancien état du gestionnaire d'erreurs de libxml
    libxml_use_internal_errors($state);
    // discard white space
    // -------------------------
    // 4- Recherche par TAG NAME
    // -------------------------
    // 4.1- article
    $domArticle 	= $domDocument->getElementsByTagName('article');
    // ---------------------
    // 4.2- childNodes de article : header / div / footer
    foreach( $domArticle->item(0)->childNodes as $ArtChildNode )
    {
    	if( !empty( $ArtChildNode->tagName ) )	// header / div / footer
    	{
    //		echo '<pre>'; print_r( $ArtChildNode->tagName ); echo '</pre>';
    		// -----------------
    		foreach( $tag_css[$ArtChildNode->tagName] as $tag => $css )
    		{
    			$nodeList = $ArtChildNode->getElementsByTagName( $tag );
    			foreach( $nodeList as $node ) 
    			{
    					$node->setAttribute( 'style', $node->getAttribute('style').$css );
    			}
    		}
    		// -----------------
    	}
    }
     
    // -------------------------
    // 5- Recherche par CLASS
    // -------------------------
    // on crée un objet DOMXPath pour pouvoir faire des requêtes XPath sur l'arbre DOM.
    $domXP 			= new DOMXPath($domDocument);
    $nodeList 		= $domXP->query('//*[@class="pClass1"]');
     
    foreach ($nodeList as $node) {
    		$node->setAttribute('style', $node->getAttribute('style').'font-weight:bold;color:pink;');
    }
     
    // -------------------------
    // 6- on reconstitue la chaîne html en concaténant les noeuds enfant de l'élément racine.
    // -------------------------
    $contentHTMLCSS = '';
    foreach ($domDocument->documentElement->childNodes as $node) {
        $contentHTMLCSS .= $domDocument->saveHTML($node);
    }
    echo $contentHTMLCSS;
    // -------------------------
    ?>
     
    </body>
    </html>
    Ce code est en partie fonctionnel :
    • pour l'instant, j'y arrive à peu près avec les noms des balises.
    • j'arrrive à traiter séparément les noeuds-enfants de <article> (header / div / footer)

    SAUF QUE...

    QUESTION 1 : Méthode
    • est-ce la bonne méthode ? (car j'ai l'impression de construire une usine à gaz)

    QUESTION 2 : Recherche par TAG NAME
    • si on prend l'exemple des 2 balises <img>, j'aimerai pouvoir différencier (styler différemment) celle dans <figure> et celle dans <div>
    • C'est-à-dire comment "déterminer" et traiter séparément les noeuds-enfants de <div class="artContenu"> ?

    QUESTION 3 : Recherche par CLASS
    • j'arrive à styler pour <p class="pClass1">,
    • mais pas pour <p class="pClass1 pClass2"> (quand il y a plusieurs classes)
    • Comment faire ? (je ne maitrise pas les query DOMXPath)


    Merci de vos éclairages...


    N.B. Pour info, l'Objectif : pouvoir envoyer des emails / newsletters HTML, formatés avec styles personnalisés.
    Dernière modification par Invité ; 15/02/2018 à 13h56.

  2. #2
    Modérateur
    Avatar de grunk
    Homme Profil pro
    Lead dév - Architecte
    Inscrit en
    Août 2003
    Messages
    6 691
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France, Côte d'Or (Bourgogne)

    Informations professionnelles :
    Activité : Lead dév - Architecte
    Secteur : Industrie

    Informations forums :
    Inscription : Août 2003
    Messages : 6 691
    Points : 20 230
    Points
    20 230
    Par défaut
    A partir du moment où tu travail avec le DOM ca devient vite une usine à gaz

    Ça serait pas plus simple de générer du css que tu viens ajouter en début d'html entre balise <style> ?
    Tu peux gérer tous les cas de parent/enfant possible avec les selecteurs css.

  3. #3
    Invité
    Invité(e)
    Par défaut
    Merci.

    Citation Envoyé par grunk Voir le message
    ...du css que tu viens ajouter en début d'html entre balise <style> ?
    Non, car certaines messageries suppriment les fichiers de styles et/ou balises <style> ajoutées dans le contenu des mails.
    (je rappelle que c'est pour envoyer des emails formatés : newsletters,...)

    A moins qu'il y ait une autre solution (?), je suis obligé de copier les styles inline (dans les balises).

  4. #4
    Expert éminent sénior
    Avatar de mathieu
    Profil pro
    Inscrit en
    Juin 2003
    Messages
    10 354
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2003
    Messages : 10 354
    Points : 15 700
    Points
    15 700
    Par défaut
    Citation Envoyé par jreaux62 Voir le message
    QUESTION 1 : Méthode
    j'ai l'impression de construire une usine à gaz
    c'est une bonne nouvelle, les webmails ont tellement de différentes façons d'afficher l'HTML et de filtrer le CSS que ça serait louche si le code avait l'air simple

    Citation Envoyé par jreaux62 Voir le message
    QUESTION 3 : Recherche par CLASS pour <p class="pClass1 pClass2"> (quand il y a plusieurs classes)
    j'ai trouvé des messages datant de 2009 avec la solution suivante, je ne sais pas s'il existe une solution plus efficace :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    $nodeList 		= $domXP->query("//*[contains(concat(' ', normalize-space(@class), ' '), ' pClass1 ')]");

  5. #5
    Invité
    Invité(e)
    Par défaut
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    $nodeList 		= $domXP->query("//*[contains(concat(' ', normalize-space(@class), ' '), ' pClass1 ')]");
    Top ! Ca a l'air de fonctionner !

    Maintenant, il va falloir que je comprenne comment ça marche : je ne maitrise pas du tout la syntaxe... et je ne sais pas où chercher/trouver (tuto ?... ?) !

  6. #6
    Expert éminent sénior
    Avatar de mathieu
    Profil pro
    Inscrit en
    Juin 2003
    Messages
    10 354
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2003
    Messages : 10 354
    Points : 15 700
    Points
    15 700
    Par défaut
    j'ai fait des essais pour ta question 2 et je suis parti d'une structure de ce genre :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
     
    $styles = [
        [
            "parents" => ["header"],
            "tag" => "h1",
            "style" => "font-size:240%;",
        ],
        [
            "parents" => ["div.artContenu", "figure"],
            "tag" => "img",
            "style" => "border:1px solid lightblue;",
        ],
        [
            "parents" => ["div.artContenu", "div"],
            "tag" => "img",
            "style" => "border:2px solid orange;",
        ],
     
    ];
    ah on me dit dans l'oreillette que ça ressemble à des sélecteurs CSS

    et ensuite ce tableau s'utilise avec la fonction récursive suivante.
    il y a un inconvénient dans ce code qui fait que l'ordre des parents n'est pas respecté donc la condition "parents" => ["div.artContenu", "figure"] s'appliquera aussi aux éléments "figure div.artContenu img" par exemple

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
     
    $domArticle     = $domDocument->getElementsByTagName('article');
     
    traitement($domArticle, []);
     
     
    function traitement(DOMNodeList $dom, array $parents) {
     
        global $styles;
     
     
        foreach ($dom as $enfant) {
     
            if (!is_a($enfant, "DOMElement")) {
                continue;
            }
     
     
            // nom de l'élément
     
            $element = $enfant->nodeName;
     
            $classes = $enfant->getAttributeNode("class");
     
            if (FALSE !== $classes) {
                $element .= "." . $classes->value;
            }
     
     
            // recherche du style qui s'applique
     
            foreach ($styles as $tabS) {
     
                if (    ($tabS["tag"] === $element)
                    &&  ([] === array_diff($tabS["parents"], $parents))
                ) {
                    // ajout du style
                    $enfant->setAttribute( 'style', $enfant->getAttribute('style') . $tabS["style"]);
                }
     
            }
     
     
            // debugage
     
            echo str_repeat("&nbsp;", count($parents) * 4);
            echo $element;
            echo " [";
            foreach ($parents as $p) {
                echo "$p, ";
            }
            echo "]";
            echo "<br/>";
     
     
            // traitement des enfants
     
            if ($enfant->hasChildNodes()) {
                traitement($enfant->childNodes, array_merge($parents, [$element]));
            }
     
        } // fin foreach ($dom as $enfant) {
     
     
    } // fin function traitement(DOMNodeList $dom, array $parents) {

  7. #7
    Expert éminent Avatar de CosmoKnacki
    Homme Profil pro
    Justicier interdimensionnel
    Inscrit en
    Mars 2009
    Messages
    2 888
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Justicier interdimensionnel

    Informations forums :
    Inscription : Mars 2009
    Messages : 2 888
    Points : 6 632
    Points
    6 632
    Par défaut
    L'impression usine à gaz est normale car DOMDocument est plutôt verbeux (mais il y a pire), et les méthodes disponibles pour atteindre un élément précis sont assez limitées, ce qui fait qu'on est obligé de sauter de nœud en nœud pour atteindre un élément profondément imbriqué. C'est justement ce que facilite XPath. Pour le tutoriel, il y a http://www.zvon.org/comp/r/tut-XPath_1.html. C'est en anglais, mais il est conçu de cette manière: présentation expéditive des principales fonctionnalités, le reste ce n'est que des exemples pour illustrer le tout.

    Ce que tu peux faire c'est de remplacer le tableau multidimensionnel par un tableau qui associe le chemin XPath à la valeur de l'attribut style:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    // ---------------------
    // 2- STYLES CSS à appliquer :
    // ---------------------
     
    $styles = [
        '/header/h1' => 'font-size:240%;color:orange;',
     
        '//div/figure'     => 'border:1px solid red;margin:20px;padding:20px;',
        '//div/figure/img' => 'border:2px dotted blue;',
        '//div/img'        => 'border:1px solid lightblue;margin:20px;padding:20px;background:lightgreen;',
        '//div/p'          => 'font-size:100%;color:grey;',
        '//div/h1'         => 'font-size:180%;color:green;',
        '//div/h2'         => 'font-size:160%;color:blue;',
        '//div/h3'         => 'font-size:140%;color:orange;',
        '//div/h4'         => 'font-size:120%;color:purple;',
     
        '/footer/p' => 'font-size:90%;background:orange;color:#fff;text-align:center;',
     
        '//*[contains(concat(" ", normalize-space(@class), " "), " pClass1 ")]' => 'font-weight:bold;color:pink;',
    ];
    // ---------------------
    // 3- PARSER avec DOMDocument
    // ---------------------
    $domDocument = new DOMDocument;
    // on évite l'affichage d'erreurs html en les redirigeant vers le gestionnaire d'erreurs de libxml. On sauvegarde l'ancien état dans $state.
    $state = libxml_use_internal_errors(true);
    // on charge le contenu HTML
    $domDocument->loadHTML( $contentHTML, LIBXML_HTML_NOIMPLIED );
    $domDocument->preserveWhiteSpace = false;
    // on restitue l'ancien état du gestionnaire d'erreurs de libxml
    libxml_use_internal_errors($state);
    $domXP = new DOMXPath($domDocument);
     
    foreach ($styles as $path => $style) {
        foreach ($domXP->query('/article' . $path) as $node) {
            if ( $node->hasAttribute('style') ) {
                $node->setAttribute('style', $node->getAttribute('style') . $style);
            } else {
                $node->setAttribute('style', $style);
            }
        }
    }
    L'avantage c'est que tu n'as pas à différencier de parties recherche par tag/recherche par class.

    L'utilisation de XPath permet aussi d'exprimer la "parenté" d'un nœud avec un autre, ce qui résout la question 2 avec: //div/img et //div/figure/img.

    Pour ce qui est de la question 3, mathieu t'a déjà donné la réponse et je ne pense pas qu'on puisse faire autrement. XPath 1.0 est assez chiche en fonctions de manipulation de chaîne, voire en fonctions tout court. Résultat, pour des tâches très simples dans d'autres langages, avec XPath on se retrouve à faire des acrobaties pas possibles pour parvenir au même résultat.
    Il reste néanmoins une lueur d'espoir pour des cas plus complexes: il est possible d'utiliser une fonction php dans une requête XPath.

    À propos de la dernière partie:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    // -------------------------
    // 6- on reconstitue la chaîne html en concaténant les noeuds enfant de l'élément racine.
    // -------------------------
    $contentHTMLCSS = '';
    foreach ($domDocument->documentElement->childNodes as $node) {
        $contentHTMLCSS .= $domDocument->saveHTML($node);
    }
    echo $contentHTMLCSS;
    Cette manipulation n'est nécessaire que lorsque le document n'a pas d'élément racine (dans ce cas, on encadre la chaîne avec une racine bidon au chargement, genre: '<div>' . $html . '</div>', puis on fait cette manip pour récupérer le tout mais sans la racine), sinon il suffit de faire: $contentHTMLCSS = $domDocument->saveHTML(); et d'ajouter l'option LIBXML_HTML_NODEFDTD à la méthode DOMDocument::loadHTML (pour éviter qu'il ajoute un DocType par défaut).

  8. #8
    Invité
    Invité(e)
    Par défaut
    Cool...
    Jusqu'ici, tout fonctionne comme je le souhaite...

    Est-il possible d'atteindre un enfant d'une classe ?

    par exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    <balise1 class="machin">
      <balise2>
          <balise3>ici l'enfant à atteindre...
    (<balise est ici un terme générique)

  9. #9
    Expert éminent Avatar de CosmoKnacki
    Homme Profil pro
    Justicier interdimensionnel
    Inscrit en
    Mars 2009
    Messages
    2 888
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Justicier interdimensionnel

    Informations forums :
    Inscription : Mars 2009
    Messages : 2 888
    Points : 6 632
    Points
    6 632
    Par défaut
    Citation Envoyé par jreaux62 Voir le message
    Cool...
    Jusqu'ici, tout fonctionne comme je le souhaite...

    Est-il possible d'atteindre un enfant d'une classe ?

    par exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    <balise1 class="machin">
      <balise2>
          <balise3>ici l'enfant à atteindre...
    (<balise est ici un terme générique)
    Oui, avec //*[@class="machin"]//balise3 pour cibler tout descendant balise3 (le // permet de cibler n'importe quel descendant sans pour autant qu'il soit un descendant direct), ou //*[@class="machin"]/balise2/balise3 pour une "parenté" plus précise, ou encore //*[@class="machin"]/balise2/* pour cibler tous les enfants (descendants directs) de balise2.

    Si "machin" est une des classes parmi d'autres dans l'attribut class, remplace [@class="machin"] par le prédicat donné par mathieu: [contains(concat(' ', normalize-space(@class), ' '), ' machin ')].

    Maintenant stricto sensu, la classe n'est pas l'ancêtre de balise3 (le seul descendant qu'a un attribut, c'est sa valeur), c'est l'élément qui possède cette classe (balise1) qui est son ancêtre.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. ajouter template html et des style css
    Par ghyath dans le forum Web & réseau
    Réponses: 1
    Dernier message: 18/04/2013, 15h13
  2. Envoyer une newsletter contenant des styles css
    Par whitespirit dans le forum Balisage (X)HTML et validation W3C
    Réponses: 9
    Dernier message: 06/03/2009, 23h49
  3. ajouter un style css a une balise <select>
    Par King_T dans le forum Mise en page CSS
    Réponses: 1
    Dernier message: 09/05/2008, 07h59

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo