Fondamentalement, c'est la question, comment est-on censé construire un objet Document à partir d'une chaîne de code HTML dynamiquement en javascript?
Il existe deux méthodes définies dans les spécifications, createDocument
à partir de DOM Core Level 2 et createHTMLDocument
à partir de HTML5. Le premier crée un document XML (y compris XHTML), le second crée un document HTML. Les deux résident, en tant que fonctions, sur l'interface DOMImplementation
.
var impl = document.implementation,
xmlDoc = impl.createDocument(namespaceURI, qualifiedNameStr, documentType),
htmlDoc = impl.createHTMLDocument(title);
En réalité, ces méthodes sont plutôt jeunes et ne sont implémentées que dans les versions récentes des navigateurs. Selon http://quirksmode.org et MDN , les navigateurs suivants prennent en charge createHTMLDocument
:
Chose intéressante, vous pouvez (en quelque sorte) créer un document HTML dans les anciennes versions d'Internet Explorer, en utilisant ActiveXObject
:
var htmlDoc = new ActiveXObject("htmlfile");
L'objet résultant sera un nouveau document, qui peut être manipulé comme n'importe quel autre document.
En supposant que vous essayez de créer un objet Document entièrement analysé à partir d'une chaîne de balisage et d'un type de contenu que vous connaissez également (peut-être parce que vous avez obtenu le code html à partir d'une requête xmlhttprequest, et donc obtenu le type de contenu dans son Content-Type
En-tête http; probablement généralement text/html
) - cela devrait être aussi simple:
var doc = (new DOMParser).parseFromString(markup, mime_type);
dans un monde futur idéal où les implémentations du navigateur DOMParser
sont aussi solides et compétentes que leur rendu de document - c'est peut-être une bonne exigence de rêve pour les futurs efforts de normalisation de HTML6
. Il s'avère cependant qu'aucun navigateur actuel ne le fait.
Vous avez probablement le problème le plus facile (mais toujours compliqué) d'avoir une chaîne html pour laquelle vous voulez obtenir un objet Document
entièrement analysé. Voici une autre façon de procéder, qui devrait également fonctionner dans tous les navigateurs - vous créez d'abord un objet HTML Document
:
var doc = document.implementation.createHTMLDocument('');
puis remplissez-le avec votre fragment html :
doc.open();
doc.write(html);
doc.close();
Maintenant, vous devriez avoir un DOM entièrement analysé dans doc, que vous pouvez exécuter alert(doc.title)
on, tranche avec des sélecteurs css comme doc.querySelectorAll('p')
ou idem XPath en utilisant doc.evaluate
.
Cela fonctionne réellement dans les navigateurs WebKit modernes comme Chrome et Safari (je viens de tester dans Chrome 22 et Safari 6 respectivement) - voici un exemple qui prend le courant le code source de la page, le recrée dans une nouvelle variable de document src
, lit son titre, le remplace par une version citée en html du même code source et affiche le résultat dans un iframe: http://codepen.io/johan/full/KLIeE
Malheureusement, je ne pense pas que d'autres navigateurs contemporains aient encore des implémentations aussi solides.
Selon la spécification ( doc ), on peut utiliser la méthode createHTMLDocument
de DOMImplementation
, accessible via document.implementation
comme suit:
var doc = document.implementation.createHTMLDocument('My title');
var body = document.createElementNS('http://www.w3.org/1999/xhtml', 'body');
doc.documentElement.appendChild(body);
// and so on
DOMImplementation
: https://developer.mozilla.org/en/DOM/document.implementationDOMImplementation.createHTMLDocument
: https://developer.mozilla.org/En/DOM/DOMImplementation.createHTMLDocumentUne réponse mise à jour pour 2014, avec l'évolution du DOMparser. Cela fonctionne dans tous les navigateurs actuels que je peux trouver, et devrait également fonctionner dans les versions antérieures d'IE, en utilisant l'approche document.implementation.createHTMLDocument ('') d'ecManaut ci-dessus.
Essentiellement, IE, Opera, Firefox peuvent tous analyser en "texte/html". Safari analyse comme "text/xml".
Attention cependant à l'analyse XML intolérante. L'analyse de Safari se décompose dans les espaces insécables et autres caractères HTML (accents français/allemands) désignés par des esperluettes. Plutôt que de gérer chaque caractère séparément, le code ci-dessous remplace toutes les esperluettes par une chaîne de caractères vide de sens "j! J!". Cette chaîne peut ensuite être restituée comme esperluette lors de l'affichage des résultats dans un navigateur (plus simple, j'ai trouvé, que d'essayer de gérer les esperluettes dans une "fausse" analyse XML).
function parseHTML(sText) {
try {
console.log("Domparser: " + typeof window.DOMParser);
if (typeof window.DOMParser !=null) {
// modern IE, Firefox, Opera parse text/html
var parser = new DOMParser();
var doc = parser.parseFromString(sText, "text/html");
if (doc != null) {
console.log("parsed as HTML");
return doc
}
else {
//replace ampersands with harmless character string to avoid XML parsing issues
sText = sText.replace(/&/gi, "j!J!");
//safari parses as text/xml
var doc = parser.parseFromString(sText, "text/xml");
console.log("parsed as XML");
return doc;
}
}
else {
// older IE
doc= document.implementation.createHTMLDocument('');
doc.write(sText);
doc.close;
return doc;
}
} catch (err) {
alert("Error parsing html:\n" + err.message);
}
}
Les éléments suivants fonctionnent dans la plupart des navigateurs courants, mais pas dans certains. C'est aussi simple que cela devrait être (mais pas):
// Fails if UA doesn't support parseFromString for text/html (e.g. IE)
function htmlToDoc(markup) {
var parser = new DOMParser();
return parser.parseFromString(markup, "text/html");
}
var htmlString = "<title>foo bar</title><div>a div</div>";
alert(htmlToDoc(htmlString).title);
Pour tenir compte des aléas des agents utilisateurs, les éléments suivants peuvent être meilleurs (veuillez noter l'attribution):
/*
* DOMParser HTML extension
* 2012-02-02
*
* By Eli Grey, http://eligrey.com
* Public domain.
* NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
*
* Modified to work with IE 9 by RobG
* 2012-08-29
*
* Notes:
*
* 1. Supplied markup should be avalid HTML document with or without HTML tags and
* no DOCTYPE (DOCTYPE support can be added, I just didn't do it)
*
* 2. Host method used where Host supports text/html
*/
/*! @source https://Gist.github.com/1129031 */
/*! @source https://developer.mozilla.org/en-US/docs/DOM/DOMParser */
/*global document, DOMParser*/
(function(DOMParser) {
"use strict";
var DOMParser_proto;
var real_parseFromString;
var textHTML; // Flag for text/html support
var textXML; // Flag for text/xml support
var htmlElInnerHTML; // Flag for support for setting html element's innerHTML
// Stop here if DOMParser not defined
if (!DOMParser) return;
// Firefox, Opera and IE throw errors on unsupported types
try {
// WebKit returns null on unsupported types
textHTML = !!(new DOMParser).parseFromString('', 'text/html');
} catch (er) {
textHTML = false;
}
// If text/html supported, don't need to do anything.
if (textHTML) return;
// Next try setting innerHTML of a created document
// IE 9 and lower will throw an error (can't set innerHTML of its HTML element)
try {
var doc = document.implementation.createHTMLDocument('');
doc.documentElement.innerHTML = '<title></title><div></div>';
htmlElInnerHTML = true;
} catch (er) {
htmlElInnerHTML = false;
}
// If if that failed, try text/xml
if (!htmlElInnerHTML) {
try {
textXML = !!(new DOMParser).parseFromString('', 'text/xml');
} catch (er) {
textHTML = false;
}
}
// Mess with DOMParser.prototype (less than optimal...) if one of the above worked
// Assume can write to the prototype, if not, make this a stand alone function
if (DOMParser.prototype && (htmlElInnerHTML || textXML)) {
DOMParser_proto = DOMParser.prototype;
real_parseFromString = DOMParser_proto.parseFromString;
DOMParser_proto.parseFromString = function (markup, type) {
// Only do this if type is text/html
if (/^\s*text\/html\s*(?:;|$)/i.test(type)) {
var doc, doc_el, first_el;
// Use innerHTML if supported
if (htmlElInnerHTML) {
doc = document.implementation.createHTMLDocument("");
doc_el = doc.documentElement;
doc_el.innerHTML = markup;
first_el = doc_el.firstElementChild;
// Otherwise use XML method
} else if (textXML) {
// Make sure markup is wrapped in HTML tags
// Should probably allow for a DOCTYPE
if (!(/^<html.*html>$/i.test(markup))) {
markup = '<html>' + markup + '<\/html>';
}
doc = (new DOMParser).parseFromString(markup, 'text/xml');
doc_el = doc.documentElement;
first_el = doc_el.firstElementChild;
}
// RG: I don't understand the point of this, I'll leave it here though
// In IE, doc_el is the HTML element and first_el is the HEAD.
//
// Is this an entire document or a fragment?
if (doc_el.childElementCount == 1 && first_el.localName.toLowerCase() == 'html') {
doc.replaceChild(first_el, doc_el);
}
return doc;
// If not text/html, send as-is to Host method
} else {
return real_parseFromString.apply(this, arguments);
}
};
}
}(DOMParser));
// Now some test code
var htmlString = '<html><head><title>foo bar</title></head><body><div>a div</div></body></html>';
var dp = new DOMParser();
var doc = dp.parseFromString(htmlString, 'text/html');
// Treat as an XML document and only use DOM Core methods
alert(doc.documentElement.getElementsByTagName('title')[0].childNodes[0].data);
Ne vous laissez pas rebuter par la quantité de code, il y a beaucoup de commentaires, il peut être raccourci un peu mais devient moins lisible.
Oh, et si le balisage est du XML valide, la vie est beaucoup plus simple:
var stringToXMLDoc = (function(global) {
// W3C DOMParser support
if (global.DOMParser) {
return function (text) {
var parser = new global.DOMParser();
return parser.parseFromString(text,"application/xml");
}
// MS ActiveXObject support
} else {
return function (text) {
var xmlDoc;
// Can't assume support and can't test, so try..catch
try {
xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
xmlDoc.async="false";
xmlDoc.loadXML(text);
} catch (e){}
return xmlDoc;
}
}
}(this));
var doc = stringToXMLDoc('<books><book title="foo"/><book title="bar"/><book title="baz"/></books>');
alert(
doc.getElementsByTagName('book')[2].getAttribute('title')
);