Après un appel ajax jQuery pour récupérer un document XHTML complet, quel est le meilleur moyen de sélectionner des éléments spécifiques dans la chaîne résultante? Peut-être y a-t-il une bibliothèque ou un plugin qui résout ce problème?
jQuery ne peut sélectionner que des éléments XHTML existant dans une chaîne s'ils sont normalement autorisés dans un div de la spécification W3C; par conséquent, je suis curieux de choisir des éléments tels que <title>
, <script>
et <style>
.
Selon la documentation jQuery:
http://docs.jquery.com/Core/jQuery#htmlownerDocument
La chaîne HTML ne peut pas contenir d'éléments non valides dans un div, tels que des éléments html, head, body ou title.
Par conséquent, puisque nous avons établi que jQuery ne fournit pas un moyen de le faire, comment choisirais-je ces éléments? Par exemple, si vous pouvez me montrer comment sélectionner le titre de la page distante, ce serait parfait!
Merci Pete
Au lieu de pirater jQuery pour ce faire, je vous suggérerais de quitter jQuery pendant une minute et d'utiliser des méthodes de dom XML brutes. En utilisant les méthodes XML Dom, vous pouvez faire ceci:
window.onload = function(){
$.ajax({
type: 'GET',
url: 'text.html',
dataType: 'html',
success: function(data) {
//cross platform xml object creation from w3schools
try //Internet Explorer
{
xmlDoc=new ActiveXObject("Microsoft.XMLDOM");
xmlDoc.async="false";
xmlDoc.loadXML(data);
}
catch(e)
{
try // Firefox, Mozilla, Opera, etc.
{
parser=new DOMParser();
xmlDoc=parser.parseFromString(data,"text/xml");
}
catch(e)
{
alert(e.message);
return;
}
}
alert(xmlDoc.getElementsByTagName("title")[0].childNodes[0].nodeValue);
}
});
}
Pas de déconner avec les iframes etc.
Seule une idée - testée dans FF/Safari - semble fonctionner si vous créez un iframe pour stocker le document temporairement. Bien sûr, si vous faites cela, il serait peut-être plus judicieux d'utiliser simplement la propriété src de l'iframe pour charger le document et faire ce que vous voulez dans sa "charge".
$(function() {
$.ajax({
type: 'GET',
url: 'result.html',
dataType: 'html',
success: function(data) {
var $frame = $("<iframe src='about:blank'/>").hide();
$frame.appendTo('body');
var doc = $frame.get(0).contentWindow.document;
doc.write(data);
var $title = $("title", doc);
alert('Title: '+$title.text() );
$frame.remove();
}
});
});
J'ai dû annexer l'iframe au corps pour que celui-ci ait une .contentWindow.
Inspiré de cette réponse , mais avec différé:
function fetchDoc(url) {
var dfd;
dfd = $.Deferred();
$.get(url).done(function (data, textStatus, jqXHR) {
var $iframe = $('<iframe style="display:none;"/>').appendTo('body');
var $doc = $iframe.contents();
var doc = $doc[0];
$iframe.load(function() {
dfd.resolveWith(doc, [data, textStatus, jqXHR]);
return $iframe.remove();
});
doc.open();
doc.write(data);
return doc.close();
}).fail(dfd.reject);
return dfd.promise();
};
Et le fumer avec:
fetchDoc('/foo.html').done(function (data, textStatus, jqXHR) {
alert($('title', this).text());
});
LIVE DEMO (cliquez sur 'Run')
Que diriez-vous d'un changement rapide de nom de balise?
$.ajax({
type : "GET",
url : 'results.html',
dataType : "html",
success: function(data) {
data = data.replace(/html/g, "xhtmlx");
data = data.replace(/head/g, "xheadx");
data = data.replace(/title/g, "xtitlex");
data = data.replace(/body/g, "xbodyx");
alert($(data).find("xtitlex").text());
}
});
Cela marche. Je viens de scinder les blocs de construction pour une meilleure lisibilité.
Vérifiez l'explication et les commentaires en ligne pour comprendre le fonctionnement de ceci et pourquoi cela doit être fait comme ça.
Bien sûr, cela ne peut pas être utilisé pour récupérer du contenu interdomaine car vous devez soit utiliser un proxy pour appeler vos appels, soit envisager une intégration similaire à flXHR (Ajax interdomaine avec Flash)
call.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>asd</title>
<script src="jquery.js" type="text/javascript"></script>
<script src="xmlDoc.js" type="text/javascript"></script>
<script src="output.js" type="text/javascript"></script>
<script src="ready.js" type="text/javascript"></script>
</head>
<body>
<div>
<input type="button" id="getit" value="GetIt" />
</div>
</body>
</html>
jquery.js is (jQuery 1.3.2 non compressé) test.html un document XHTML valide
xmlDoc.js
// helper function to create XMLDocument out of a string
jQuery.createXMLDocument = function( s ) {
var xmlDoc;
// is it a IE?
if ( window.ActiveXObject ) {
xmlDoc = new ActiveXObject('Microsoft.XMLDOM');
xmlDoc.async = "false";
// prevent erros as IE tries to resolve the URL in the DOCTYPE
xmlDoc.resolveExternals = false;
xmlDoc.validateOnParse = false;
xmlDoc.loadXML(s);
} else {
// non IE. give me DOMParser
// theoretically this else branch should never be called
// but just in case.
xmlDoc = ( new DOMParser() ).parseFromString( s, "text/xml" );
}
return xmlDoc;
};
output.js
// Output the title of the loaded page
// And get the script-tags and output either the
// src attribute or code
function headerData(data) {
// give me the head element
var x = jQuery("head", data).eq(0);
// output title
alert(jQuery("title", x).eq(0).text());
// for all scripttags which include a file out put src
jQuery("script[src]", x).each(function(index) {
alert((index+1)+" "+jQuery.attr(this, 'src'));
});
// for all scripttags which are inline javascript output code
jQuery("script:not([src])", x).each(function(index) {
alert(this.text);
});
}
ready.js
$(document).ready(function() {
$('#getit').click(function() {
$.ajax({
type : "GET",
url : 'test.html',
dataType : "xml",
// overwrite content-type returned by server to ensure
// the response getst treated as xml
beforeSend: function(xhr) {
// IE doesn't support this so check before using
if (xhr.overrideMimeType) {
xhr.overrideMimeType('text/xml');
}
},
success: function(data) {
headerData(data);
},
error : function(xhr, textStatus, errorThrown) {
// if loading the response as xml failed try it manually
// in theory this should only happen for IE
// maybe some
if (textStatus == 'parsererror') {
var xmlDoc = jQuery.createXMLDocument(xhr.responseText);
headerData(xmlDoc);
} else {
alert("Failed: " + textStatus + " " + errorThrown);
}
}
});
});
});
Dans Opera, tout fonctionne sans les fonctions createXMLDocument
et beforeSend
.
Cette astuce supplémentaire est nécessaire pour Firefox (3.0.11) et IE6 (impossible de tester IE7, IE8, les autres navigateurs) car ils ont un problème lorsque le Content-Type:
renvoyé par le serveur n'indique pas qu'il s'agit de xml. Mon serveur Web a renvoyé Content-Type: text/html; charset=UTF-8
pour test.html.
Dans ces deux navigateurs, jQuery a appelé le rappel error
avec textStatus
en disant parsererror
. Parce que dans la ligne 3706 dans jQuery.js
data = xml ? xhr.responseXML : xhr.responseText;
data
est défini sur null. Comme dans FF et IE, le xhr.responseXML
est nul. Cela est dû au fait qu’ils ne comprennent pas que les données renvoyées sont au format XML (comme le fait Opera). Et seul xhr.responseText
est défini avec le code xhtml complet. Comme les données sont nulles, la ligne 3708
if ( xml && data.documentElement.tagName == "parsererror" )
lève une exception qui est interceptée à la ligne 3584 et dont le statut est défini sur parsererror
.
En FF, je peux résoudre le problème en utilisant la fonction overrideMimeType()
avant d’envoyer la demande.
Mais IE ne prend pas en charge cette fonction sur l'objet XMLHttpRequest. Je dois donc générer moi-même le XMLDocument si le rappel d'erreur est exécuté et que l'erreur est parsererror
.
exemple pour test.html
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>Plugins | jQuery Plugins</title>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript">var imagePath = '/content/img/so/';</script>
</head>
<body>
</body>
</html>
Sans vergogne copié et adapté d’une autre de mes réponses ( Exemple simple jQuery ajax ne trouvant pas d’éléments dans le code HTML retourné ), cela récupère le code HTML de la page distante, puis la fonction parseHTML crée un élément div temporaire pour elle et place le lot. inside le traverse et renvoie l’élément demandé. jQuery alerte ensuite le texte () à l'intérieur.
$(document).ready(function(){
$('input').click(function(){
$.ajax({
type : "POST",
url : 'ajaxtestload.html',
dataType : "html",
success: function(data) {
alert( data ); // shows whole dom
var gotcha = parseHTML(data, 'TITLE'); // nodeName property returns uppercase
if (gotcha) {
alert($(gotcha).html()); // returns null
}else{
alert('Tag not found.');
}
},
error : function() {
alert("Sorry, The requested property could not be found.");
}
});
});
});
function parseHTML(html, tagName) {
var root = document.createElement("div");
root.innerHTML = html;
// Get all child nodes of root div
var allChilds = root.childNodes;
for (var i = 0; i < allChilds.length; i++) {
if (allChilds[i].nodeName == tagName) {
return allChilds[i];
}
}
return false;
}
Pour obtenir plusieurs éléments ou une liste de balises de script, disons, je pense qu'il faudrait améliorer la fonction parseHTML, mais bon - preuve de concept :-)
Que diriez-vous de ceci: Load XML from string
Après avoir analysé la chaîne XML en un XML DOM
, j'utiliserais directement jQuery
(vous pouvez le faire en fournissant un contexte au sélecteur jQUery
, tel que $(':title', xdoc.rootElement)
ou en utilisant XPath
(fonctionne dans Firefox; il y a supposément des bibliothèques pour IE mais je n’ai pas eu beaucoup de succès avec eux).
Si vous vouliez trouver la valeur de champs nommés spécifiquement (c'est-à-dire les entrées d'un formulaire), quelque chose comme ceci les trouverait pour vous:
var fields = ["firstname","surname", ...."foo"];
function findFields(form, fields) {
var form = $(form);
fields.forEach(function(field) {
var val = form.find("[name="+field+"]").val();
....
$.get('yourpage.html',function(data){
var content = $('<div/>').append(data).find('#yourelement').html();
});
Vous pouvez également simplement envelopper temporairement à l'intérieur d'une div. Vous n'avez même pas besoin de l'ajouter au DOM.