web-dev-qa-db-fra.com

Comment fermer des balises HTML non fermées?

Chaque fois que nous récupérons du contenu saisi par l'utilisateur avec des modifications de la base de données ou de sources similaires, nous pouvons récupérer la partie contenant uniquement la balise d'ouverture mais pas de fermeture.

Cela peut entraver la mise en page actuelle du site. 

Existe-t-il un moyen de résoudre ce problème côté serveur ou côté serveur?

34
Starx

J'ai trouvé une bonne réponse pour celui-ci:

Utilisez PHP 5 et utilisez la méthode loadHTML () de l'objet DOMDocument. Cette auto analyse le code HTML mal formé et un appel ultérieur à saveXML () affichera le code HTML valide. Les fonctions DOM peuvent être trouvées ici:

http://www.php.net/dom

L'utilisation de ceci:

$doc = new DOMDocument();
$doc->loadHTML($yourText);
$yourText = $doc->saveHTML();
24
KJS

J'ai une solution pour php

<?php
    // close opened html tags
    function closetags ( $html )
        {
        #put all opened tags into an array
        preg_match_all ( "#<([a-z]+)( .*)?(?!/)>#iU", $html, $result );
        $openedtags = $result[1];

        #put all closed tags into an array
        preg_match_all ( "#</([a-z]+)>#iU", $html, $result );
        $closedtags = $result[1];
        $len_opened = count ( $openedtags );

        # all tags are closed
        if( count ( $closedtags ) == $len_opened )
        {
            return $html;
        }
        $openedtags = array_reverse ( $openedtags );

        # close tags
        for( $i = 0; $i < $len_opened; $i++ )
        {
            if ( !in_array ( $openedtags[$i], $closedtags ) )
            {
                $html .= "</" . $openedtags[$i] . ">";
            }
            else
            {
                unset ( $closedtags[array_search ( $openedtags[$i], $closedtags)] );
            }
        }
        return $html;
    }
    // close opened html tags
?>

Vous pouvez utiliser cette fonction comme

   <?php echo closetags("your content <p>test test"); ?>
15
kamal

Vous pouvez utiliser Tidy :

Tidy est une liaison pour l'utilitaire de nettoyage et de réparation Tidy HTML qui vous permet non seulement de nettoyer et de manipuler des documents HTML, mais également de parcourir l'arborescence de documents. 

ou HTMLPurifier

Le purificateur HTML est conforme aux normes Bibliothèque de filtres HTML écrite en PHP. HTML Purifier ne supprimera pas seulement tous les programmes malveillants code (mieux connu sous le nom de XSS) avec une vérification approfondie, liste blanche sécurisée mais permissive, il s'assurera également que vos documents sont conforme aux normes, quelque chose qui n’est réalisable qu’avec un connaissance approfondie des spécifications du W3C.

15
Gordon

Outre les outils côté serveur tels que Tidy, vous pouvez également utiliser le navigateur de l'utilisateur pour effectuer une partie du nettoyage à votre place. L'un des avantages de innerHTML est qu'il appliquera la même réparation à la volée au contenu dynamique qu'aux pages HTML. Ce code fonctionne plutôt bien (avec deux mises en garde) et rien n’est écrit sur la page:

var divTemp = document.createElement('div');
divTemp.innerHTML = '<p id="myPara">these <i>tags aren\'t <strong> closed';
console.log(divTemp.innerHTML); 

Les mises en garde:

  1. Les différents navigateurs renverront des chaînes différentes. Ce n'est pas si grave, sauf dans le cas d'IE, qui renverra les balises en majuscules et supprimera les guillemets des attributs de balises, ce qui ne passera pas la validation. La solution ici consiste à effectuer un nettoyage simple côté serveur. Mais au moins le document sera correctement structuré en XML.

  2. Je suppose que vous devrez peut-être attendre plus longtemps avant de lire innerHTML - donnez au navigateur une chance de digérer la chaîne - ou vous risquez de récupérer exactement ce qui a été mis. J'ai juste essayé IE8 et il ressemble à la chaîne obtient analysé immédiatement, mais je ne suis pas si sûr sur IE6. Il serait probablement préférable de lire le code innerHTML après un délai (ou de le jeter dans un setTimeout () pour le forcer à la fin de la file d'attente). 

Je vous recommanderais de suivre les conseils de @ Gordon et d'utiliser Tidy si vous y avez accès (cela prend moins de travail à mettre en œuvre). Sinon, utilisez innerHTML et écrivez votre propre fonction de nettoyage en PHP.

Et bien que cela ne fasse pas partie de votre question, comme il s’agit d’un CMS, envisagez également d’utiliser l’éditeur YUI 2 Rich Text Editor pour des éléments de ce type. C'est assez facile à mettre en œuvre, assez facile à personnaliser, l'interface est très familière à la plupart des utilisateurs et elle crache du code parfaitement valide. Il existe plusieurs autres éditeurs de texte enrichis sur le marché, mais YUI possède la meilleure licence et est la plus puissante que j'ai jamais vue.

6
Andrew

Pour les fragments HTML, et à partir de Réponse de KJS J'ai eu du succès avec les éléments suivants lorsque le fragment a un élément racine:

$dom = new DOMDocument();
$dom->loadHTML($string);
$body = $dom->documentElement->firstChild->firstChild;
$string = $dom->saveHTML($body);

Sans élément racine, cela est possible (mais semble ne contenir que le premier nœud enfant texte dans les balises p dans text <p>para</p> text):

$dom = new DOMDocument();
$dom->loadHTML($string);
$bodyChildNodes = $dom->documentElement->firstChild->childNodes;

$string = '';
foreach ($bodyChildNodes as $node){
   $string .= $dom->saveHTML($node);
}

Ou mieux encore, à partir de PHP> = 5.4 et libxml> = 2.7.8 (2.7.7 pour LIBXML_HTML_NOIMPLIED):

$dom = new DOMDocument();

// Load with no html/body tags and do not add a default dtd
$dom->loadHTML($string, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);

$string = $dom->saveHTML();    
6
Arth

Une meilleure fonction PHP pour supprimer les balises non ouvertes/non fermées de webmaster-glossar.de (moi)

function closetag($html){
    $html_new = $html;
    preg_match_all ( "#<([a-z]+)( .*)?(?!/)>#iU", $html, $result1);
    preg_match_all ( "#</([a-z]+)>#iU", $html, $result2);
    $results_start = $result1[1];
    $results_end = $result2[1];
    foreach($results_start AS $startag){
        if(!in_array($startag, $results_end)){
            $html_new = str_replace('<'.$startag.'>', '', $html_new);
        }
    }
    foreach($results_end AS $endtag){
        if(!in_array($endtag, $results_start)){
            $html_new = str_replace('</'.$endtag.'>', '', $html_new);
        }
    }
    return $html_new;
}

utilisez cette fonction comme:

closetag('i <b>love</b> my <strike>cat'); 
#output: i <b>love</b> my cat

closetag('i <b>love</b> my cat</strike>'); 
#output: i <b>love</b> my cat
3
Marcus

J'avais l'habitude de la méthode native DOMDocument, mais avec quelques améliorations pour la sécurité.

Notez que les autres réponses qui utilisent DOMDocument ne considèrent pas les brins HTML tels que 

This is a <em>HTML</em> strand

Ce qui précède entraînera réellement 

<p>This is a <em>HTML</em> strand

Ma solution est ci-dessous

function closeDanglingTags($html) {
    if (strpos($html, '<') || strpos($html, '>')) {
        // There are definitiley HTML tags
        $wrapped = false;
        if (strpos(trim($html), '<') !== 0) {
            // The HTML starts with a text node. Wrap it in an element with an id to prevent the software wrapping it with a <p>
            //  that we know nothing about and cannot safely retrieve
            $html = cHE::getDivHtml($html, null, 'closedanglingtagswrapper');
            $wrapped = true;
        }
        $doc = new DOMDocument();
        $doc->encoding = 'utf-8';
        @$doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
        if ($doc->firstChild) {
            // Test whether the firstchild is definitely a DOMDocumentType
            if ($doc->firstChild instanceof DOMDocumentType) {
                // Remove the added doctype
                $doc->removeChild($doc->firstChild);
            }
        }
        if ($wrapped) {
            // The contents originally started with a text node and was wrapped in a div#plasmappclibtextwrap. Take the contents
            //  out of that div
            $node = $doc->getElementById('closedanglingtagswrapper');
            $children = $node->childNodes;  // The contents of the div. Equivalent to $('selector').children()
            $doc = new DOMDocument();   // Create a new document to add the contents to, equiv. to "var doc = $('<html></html>');"
            foreach ($children as $childnode) {
                $doc->appendChild($doc->importNode($childnode, true)); // E.g. doc.append()
            }
        }
        // Remove the added html,body tags
        return trim(str_replace(array('<html><body>', '</body></html>'), '', html_entity_decode($doc->saveHTML())));
    } else {
        return $html;
    }
}
0
Luke Madhanga

Erik Arvidsson a écrit un analyseur Nice HTML SAX en 2004. http://erik.eae.net/archives/2004/11/20/12.18.31/

Il garde une trace des balises ouvertes. Ainsi, avec un gestionnaire SAX minimaliste, il est possible d’insérer des balises de fermeture à la position correcte:

function tidyHTML(html) {
    var output = '';
    HTMLParser(html, {
        comment: function(text) {
            // filter html comments
        },
        chars: function(text) {
            output += text;
        },
        start: function(tagName, attrs, unary) {
            output += '<' + tagName;
            for (var i = 0; i < attrs.length; i++) {
                output += ' ' + attrs[i].name + '=';
                if (attrs[i].value.indexOf('"') === -1) {
                    output += '"' + attrs[i].value + '"';
                } else if (attrs[i].value.indexOf('\'') === -1) {
                    output += '\'' + attrs[i].value + '\'';
                } else { // value contains " and ' so it cannot contain spaces
                    output += attrs[i].value;
                }
            }
            output += '>';
        },
        end: function(tagName) {
            output += '</' + tagName + '>';
        }
    });
    return output;
}
0
Robert