web-dev-qa-db-fra.com

Extraire le DOM actuel et l’imprimer sous forme de chaîne, avec les styles intacts

J'aimerais pouvoir prendre mon DOM tel quel et le convertir en chaîne. Supposons que j'ouvre l'inspecteur et modifie la propriété margin-left d'un élément particulier. Ce changement devrait être reflété dans ma chaîne.

La fonction doit correctement prendre tous les styles actuellement appliqués à un élément (à l'exception des valeurs par défaut) et les inclure dans le style en ligne de cet élément.

J'ai écrit une "solution" qui s'est révélée inadéquate. La fonction getMatchedCSSRules de webkit est extrêmement minutieuse et je n’ai pas été en mesure de déterminer pourquoi elle fonctionne parfois et ne fonctionne pas d’autres fois. Par conséquent, je voudrais éviter d'utiliser cette fonction, sauf si cela fonctionne 100% du temps. De même, la fonction getComputedStyle a ses propres problèmes. Si vous utilisez l'inspecteur pour modifier l'élément #footer sur cette page en 7px solid red plutôt que 7px solid black, la modification sera reflétée lorsque j'exécuterai getComputedStyle(document.getElementById('footer')).cssText dans la console, mais cela me donnera également un hôte de propriétés héritées qui n'ont jamais été modifiées par soit l’utilisateur utilisant l’inspecteur, soit les feuilles de style de la page.

Je recherche une solution compatible avec webkit - la compatibilité entre navigateurs n'est pas un problème pour le moment. 

Je vous remercie!

47
D-Nice

Je pense que cela pourrait être une solution (cela m'a pris presque une journée entière!).

Il renvoie une chaîne représentant le DOM de tout élément, Avec tous les styles externes inclus dans les attributs "style" sauf les valeurs par défaut, Et ne modifie pas cet élément de façon permanente.

Par exemple: console.log(document.body.serializeWithStyles());

Vous pouvez charger ce code dans la ligne de commande de Web Inspector ou à partir d'une balise de script dans l'élément body, mais PAS dans l'élément head car cela nécessite l'existence de document.body.

Je l'ai testé sur le bureau Safari 5 (je n'ai pas la version mobile). 

Cela fonctionne comme ceci:

Pour chaque élément du DOM:
1) en mettant en cache la valeur de la propriété style.cssText, qui représente le style en ligne, dans un tableau;
2) appel de getComputedStyle sur l'élément;
3) vérifiant si nous avons la table de consultation des valeurs par défaut css correspondant au nom de la balise de cet élément;
4) le construire sinon;
5) en parcourant le résultat, en recherchant les valeurs autres que celles par défaut à l'aide de la table de consultation;
6) appliquant ces valeurs de style autres que celles par défaut à l'élément.
Ensuite, stocker le fichier outerHTML en tant que résultat;
Pour chaque élément, restauration des styles en ligne à partir du cache;
Renvoyer le résultat précédemment stocké.

Le code:

Element.prototype.serializeWithStyles = (function () {  

    // Mapping between tag names and css default values lookup tables. This allows to exclude default values in the result.
    var defaultStylesByTagName = {};

    // Styles inherited from style sheets will not be rendered for elements with these tag names
    var noStyleTags = {"BASE":true,"HEAD":true,"HTML":true,"META":true,"NOFRAME":true,"NOSCRIPT":true,"PARAM":true,"SCRIPT":true,"STYLE":true,"TITLE":true};

    // This list determines which css default values lookup tables are precomputed at load time
    // Lookup tables for other tag names will be automatically built at runtime if needed
    var tagNames = ["A","ABBR","ADDRESS","AREA","ARTICLE","ASIDE","AUDIO","B","BASE","BDI","BDO","BLOCKQUOTE","BODY","BR","BUTTON","CANVAS","CAPTION","CENTER","CITE","CODE","COL","COLGROUP","COMMAND","DATALIST","DD","DEL","DETAILS","DFN","DIV","DL","DT","EM","EMBED","FIELDSET","FIGCAPTION","FIGURE","FONT","FOOTER","FORM","H1","H2","H3","H4","H5","H6","HEAD","HEADER","HGROUP","HR","HTML","I","IFRAME","IMG","INPUT","INS","KBD","KEYGEN","LABEL","LEGEND","LI","LINK","MAP","MARK","MATH","MENU","META","METER","NAV","NOBR","NOSCRIPT","OBJECT","OL","OPTION","OPTGROUP","OUTPUT","P","PARAM","PRE","PROGRESS","Q","RP","RT","Ruby","S","SAMP","SCRIPT","SECTION","SELECT","SMALL","SOURCE","SPAN","STRONG","STYLE","SUB","SUMMARY","SUP","SVG","TABLE","TBODY","TD","TEXTAREA","TFOOT","TH","THEAD","TIME","TITLE","TR","TRACK","U","UL","VAR","VIDEO","WBR"];

    // Precompute the lookup tables.
    for (var i = 0; i < tagNames.length; i++) {
        if(!noStyleTags[tagNames[i]]) {
            defaultStylesByTagName[tagNames[i]] = computeDefaultStyleByTagName(tagNames[i]);
        }
    }

    function computeDefaultStyleByTagName(tagName) {
        var defaultStyle = {};
        var element = document.body.appendChild(document.createElement(tagName));
        var computedStyle = getComputedStyle(element);
        for (var i = 0; i < computedStyle.length; i++) {
            defaultStyle[computedStyle[i]] = computedStyle[computedStyle[i]];
        }
        document.body.removeChild(element); 
        return defaultStyle;
    }

    function getDefaultStyleByTagName(tagName) {
        tagName = tagName.toUpperCase();
        if (!defaultStylesByTagName[tagName]) {
            defaultStylesByTagName[tagName] = computeDefaultStyleByTagName(tagName);
        }
        return defaultStylesByTagName[tagName];
    }

    return function serializeWithStyles() {
        if (this.nodeType !== Node.ELEMENT_NODE) { throw new TypeError(); }
        var cssTexts = [];
        var elements = this.querySelectorAll("*");
        for ( var i = 0; i < elements.length; i++ ) {
            var e = elements[i];
            if (!noStyleTags[e.tagName]) {
                var computedStyle = getComputedStyle(e);
                var defaultStyle = getDefaultStyleByTagName(e.tagName);
                cssTexts[i] = e.style.cssText;
                for (var ii = 0; ii < computedStyle.length; ii++) {
                    var cssPropName = computedStyle[ii];
                    if (computedStyle[cssPropName] !== defaultStyle[cssPropName]) {
                        e.style[cssPropName] = computedStyle[cssPropName];
                    }
                }
            }
        }
        var result = this.outerHTML;
        for ( var i = 0; i < elements.length; i++ ) {
            elements[i].style.cssText = cssTexts[i];
        }
        return result;
    }
})();
72
Luc125

Ne pouvez-vous pas simplement faire document.getElementsByTagName ('body') [0] .innerHTML? Lorsque j'apporte des modifications dans l'inspecteur, puis que je saisis le code javascript ci-dessus dans la console, le code HTML mis à jour est renvoyé.

EDIT: Je viens d'essayer de mettre ce script dans une fonction et de l'attacher à un événement onclick. Fait quelques mises à jour dans l'inspecteur, cliqué sur le bouton, et cela a fonctionné:

HTML

<button onclick="printDOM()">Print DOM</button>

Javascript

function printDOM() {
    console.log(document.getElementsByTagName('body')[0].innerHTML) ;
}
9
squidbe

Si vous souhaitez capturer toute la page, il est plus facile d’obtenir toutes les feuilles de style non-inline et inline.

L’approche retenue dans la réponse acceptée est magnifique, mais assez lente et touche l’ensemble du document.

J'ai adopté l'approche suivante pour capturer une page avec style:

  1. document.documentElement.outerHTML;

  2. obtenir toutes les feuilles de style de l'API document.styleSheets

Le long des lignes de:

function captureCss(){
    var cssrules = "";
    var sheets = document.styleSheets;
    for(var i = 0; i<sheets.length; i++){
        if(!sheets[i].disabled && sheets[i].href != null) { // or sheets[i].href.nodeName == 'LINK'
            if(sheets[i].rules == null){ // can be null because of cross Origin policy
                try{
                    var fetched = XHR GET(sheets[i].href); // works nicely because it hits the cache
                    if(fetched){
                        cssrules += "<style>\n"+fetched+"\n</style>\n"
                    }
                }catch(e){
                    console.log(e);
                }
                continue;
            }
            for(var j=0;j<sheets[i].rules.length;j++){
                cssrules += "<style>\n"+sheets[i].rules[j].cssText+"\n</style>\n"
            }
        }
    }
    return cssrules;
}
  1. Ajoutez la cssrules capturée en tant que première chose de l'en-tête du texte html outerHtml

De cette façon, vous obtenez une page stylée autonome.

Ceci est évidemment moins applicable pour le contenu partiel.

3
sleeplessnerd

OK, peut-être qu'il me manque quelque chose ici, mais la chaîne que vous voulez n'est-elle pas simplement document.documentElement.innerHTML? Un test rapide avec Chrome vérifie qu'il enregistre les modifications apportées aux outils de développement afin d'attribuer un style aux attributs que vous décrivez. Les noms de classe attribués ne sont pas développés (vous n'aurez aucune idée par exemple de ce que class="superfuntime" est en train de faire), mais si je lis bien votre question, vous n'en avez pas indiqué le besoin. 

1
JURU

Sur la base de la réponse de Luc125, j'ai créé une extension pour les outils de développement pour Chrome qui intègre ce code pour la capture de styles et de balises pour un fragment de page. L'extension se trouve dans Chrome Web Store et se trouve sur Github . L'option de sortie "Styles calculés" utilise cette méthode.

Extension Screenshot

1
ifugu

Internet Explorer -> Outils de développement -> DOM Explorer

Sélectionnez un élément et faites un clic droit -> "Copier un élément avec des styles".

1
Ionut

Peut-être que la Google Closure Library a une solution pour vous.

Il y a du code qui semble faire ce dont vous avez besoin, c’est-à-dire calculer les règles CSS pour reproduire la même apparence d’un élément en dehors de sa position actuelle dans le dom un éditeur en ligne transparent).

Citant du fichier source style.js :

Provides utility routines for copying modified
CSSRule objects from the parent document into iframes so that any
content in the iframe will be styled as if it was inline in the parent
document.

<p>
For example, you might have this CSS rule:

#content .highlighted { background-color: yellow; }

And this DOM structure:

<div id="content">
  <iframe />
</div>

Then inside the iframe you have:

<body>
<div class="highlighted">
</body>

If you copied the CSS rule directly into the iframe, it wouldn't match the
.highlighted div. So we rewrite the original stylesheets based on the
context where the iframe is going to be inserted. In this case the CSS
selector would be rewritten to:

body .highlighted { background-color: yellow; }
</p>
1
alienhard

Fonction Chrome - Impression du DOM:
L'indicateur --dump-dom imprime document.body.innerHTML sur la sortie standard:

chrome --headless --disable-gpu --dump-dom https://www.chromestatus.com/

Lire plus

0
saulsluz