web-dev-qa-db-fra.com

Puis-je charger un document HTML entier dans un fragment de document dans Internet Explorer?

Voici quelque chose qui me pose un peu de difficulté. J'ai un script local côté client qui doit permettre à un utilisateur d'extraire une page Web distante et de rechercher des formulaires dans cette page résultante. Pour ce faire (sans regex), je dois analyser le document dans un objet DOM totalement traversable.

Quelques limites sur lesquelles j'aimerais insister:

  • Je ne veux pas utiliser de librairies (comme jQuery). Il y a trop de ballonnement pour ce que je dois faire ici.
  • Les scripts de la page distante ne doivent en aucun cas être exécutés (pour des raisons de sécurité).
  • Les API DOM, telles que getElementsByTagName, doivent être disponibles.
  • Il doit seulement fonctionner dans Internet Explorer, mais dans 7 au minimum.
  • Imaginons que je n'ai pas accès à un serveur. Oui, mais je ne peux pas l'utiliser pour ça.

Ce que j'ai essayé

En supposant que j'ai une chaîne de document HTML complète (y compris la déclaration DOCTYPE) dans la variable html, voici ce que j'ai déjà essayé:

var frag = document.createDocumentFragment(),
div  = frag.appendChild(document.createElement("div"));

div.outerHTML = html;
//-> results in an empty fragment

div.insertAdjacentHTML("afterEnd", html);
//-> HTML is not added to the fragment

div.innerHTML = html;
//-> Error (expected, but I tried it anyway)

var doc = new ActiveXObject("htmlfile");
doc.write(html);
doc.close();
//-> JavaScript executes

J'ai également essayé d'extraire les codes <head> et <body> du code HTML et de les ajouter à un élément <HTML> dans le fragment, toujours pas de chance.

Quelqu'un a-t-il une idée?

42
Andy E

Vous ne savez pas pourquoi vous manipulez documentFragments, vous pouvez simplement définir le texte HTML comme la variable innerHTML d'un nouvel élément div. Ensuite, vous pouvez utiliser cet élément div pour getElementsByTagName etc. sans ajouter le div à DOM:

var htmlText= '<html><head><title>Test</title></head><body><div id="test_ele1">this is test_ele1 content</div><div id="test_ele2">this is test_ele content2</div></body></html>';

var d = document.createElement('div');
d.innerHTML = htmlText;

console.log(d.getElementsByTagName('div'));

Si vous êtes vraiment marié à l'idée d'un documentFragment, vous pouvez utiliser ce code, mais vous devrez quand même l'envelopper dans un div pour obtenir les fonctions DOM recherchées

function makeDocumentFragment(htmlText) {
    var range = document.createRange();
    var frag = range.createContextualFragment(htmlText);
    var d = document.createElement('div');
    d.appendChild(frag);
    return d;
}
4
Chris Baker

Je ne sais pas si IE supporte document.implementation.createHTMLDocument, mais si c'est le cas, utilisez cet algorithme (adapté de mon extension HTML DOMParser ). Notez que le DOCTYPE ne sera pas conservé .:

var
      doc = document.implementation.createHTMLDocument("")
    , doc_elt = doc.documentElement
    , first_elt
;
doc_elt.innerHTML = your_html_here;
first_elt = doc_elt.firstElementChild;
if ( // are we dealing with an entire document or a fragment?
       doc_elt.childElementCount === 1
    && first_elt.tagName.toLowerCase() === "html"
) {
    doc.replaceChild(first_elt, doc_elt);
}

// doc is an HTML document
// you can now reference stuff like doc.title, etc.
2
Eli Grey

En supposant que le code HTML soit également valide, vous pouvez utiliser loadXML ()

1
Dr.Molle

Utiliser des capacités DOM HTML complètes sans déclencher de requêtes, sans avoir à gérer d'incompatibilités:

var doc = document.cloneNode();
if (!doc.documentElement) {
    doc.appendChild(doc.createElement('html'));
    doc.documentElement.appendChild(doc.createElement('head'));
    doc.documentElement.appendChild(doc.createElement('body'));
}

Tous ensemble! doc est un document html, mais il n'est pas en ligne.

0
Jérémy Lal

DocumentFragment ne supporte pas getElementsByTagName - cela n'est supporté que par Document.

Vous devrez peut-être utiliser une bibliothèque comme jsdom , qui fournit une implémentation du DOM et dans laquelle vous pouvez effectuer une recherche à l'aide de getElementsByTagName et d'autres API DOM. Et vous pouvez le configurer pour ne pas exécuter de scripts. Oui, c'est «lourd» et je ne sais pas si cela fonctionne dans IE 7.

0
Javier Pedemonte

Je me suis promené sur cette page, je suis un peu en retard pour être utile :) mais ce qui suit devrait aider toute personne confrontée à un problème similaire à l'avenir ... cependant IE7/8 devrait vraiment être ignoré maintenant et il existe de bien meilleures méthodes supportées par les navigateurs plus modernes.

Les travaux suivants sur presque tout ce que j'ai testé - les deux seuls inconvénients sont:

  1. J'ai ajouté des fonctions getElementById et getElementsByName sur mesure à l'élément div de racine, afin qu'elles n'apparaissent pas comme prévu dans l'arborescence (sauf si le code est modifié pour répondre à cela) .

  2. Le doctype sera ignoré - mais je ne pense pas que cela fera beaucoup de différence car mon expérience est que le doctype n'aura aucune incidence sur la structure du dom, ni sur la manière dont il est rendu (ce qui ne se produira évidemment pas avec cette méthode) .

En gros, le système repose sur le fait que <tag> et <namespace:tag> sont traités différemment par les agents utilisateurs. Comme il a été constaté, certaines balises spéciales ne peuvent pas exister dans un élément div et sont donc supprimées. Les éléments namespaced peuvent être placés n'importe où (sauf indication contraire d'une DTD) . Bien que ces balises d'espace de noms ne se comportent pas réellement comme les vraies balises en question, étant donné que nous ne les utilisons réellement que pour leur position structurelle dans le document, cela ne pose pas vraiment de problème.

le balisage et le code sont les suivants:

<!DOCTYPE html>
<html>
<head>
<script>

  /// function for parsing HTML source to a dom structure
  /// Tested in Mac OSX, Win 7, Win XP with FF, IE 7/8/9, 
  /// Chrome, Safari & Opera.
  function parseHTML(src){

    /// create a random div, this will be our root
    var div = document.createElement('div'),
        /// specificy our namespace prefix
        ns = 'faux:',
        /// state which tags we will treat as "special"
        stn = ['html','head','body','title'];
        /// the reg exp for replacing the special tags
        re = new RegExp('<(/?)('+stn.join('|')+')([^>]*)?>','gi'),
        /// remember the getElementsByTagName function before we override it
        gtn = div.getElementsByTagName;

    /// a quick function to namespace certain tag names
    var nspace = function(tn){
      if ( stn.indexOf ) {
        return stn.indexOf(tn) != -1 ? ns + tn : tn;
      }
      else {
        return ('|'+stn.join('|')+'|').indexOf(tn) != -1 ? ns + tn : tn;
      }
    };

    /// search and replace our source so that special tags are namespaced
    /// &nbsp; required for IE7/8 to render tags before first text found
    /// <faux:check /> tag added so we can test how namespaces work
    src = '&nbsp;<'+ns+'check />' + src.replace(re,'<$1'+ns+'$2$3>');
    /// inject to the div
    div.innerHTML = src;
    /// quick test to see how we support namespaces in TagName searches
    if ( !div.getElementsByTagName(ns+'check').length ) {
      ns = '';
    }

    /// create our replacement getByName and getById functions
    var createGetElementByAttr = function(attr, collect){
      var func = function(a,w){
        var i,c,e,f,l,o; w = w||[];
        if ( this.nodeType == 1 ) {
          if ( this.getAttribute(attr) == a ) {
            if ( collect ) {
              w.Push(this);
            }
            else {
              return this;
            }
          }
        }
        else {
          return false;
        }
        if ( (c = this.childNodes) && (l = c.length) ) {
          for( i=0; i<l; i++ ){
            if( (e = c[i]) && (e.nodeType == 1) ) {
              if ( (f = func.call( e, a, w )) && !collect ) {
                return f;
              }
            }
          }
        }
        return (w.length?w:false);
      }
      return func;
    }

    /// apply these replacement functions to the div container, obviously 
    /// you could add these to prototypes for browsers the support element 
    /// constructors. For other browsers you could step each element and 
    /// apply the functions through-out the node tree... however this would  
    /// be quite messy, far better just to always call from the root node - 
    /// or use div.getElementsByTagName.call( localElement, 'tag' );
    div.getElementsByTagName = function(t){return gtn.call(this,nspace(t));}
    div.getElementsByName    = createGetElementByAttr('name', true);
    div.getElementById       = createGetElementByAttr('id', false);

    /// return the final element
    return div;
  }

  window.onload = function(){

    /// parse the HTML source into a node tree
    var dom = parseHTML( document.getElementById('source').innerHTML );

    /// test some look ups :)
    var a = dom.getElementsByTagName('head'),
        b = dom.getElementsByTagName('title'),
        c = dom.getElementsByTagName('script'),
        d = dom.getElementById('body');

    /// alert the result
    alert(a[0].innerHTML);
    alert(b[0].innerHTML);
    alert(c[0].innerHTML);
    alert(d.innerHTML);

  }
</script>
</head>
<body>
  <xmp id="source">
    <!DOCTYPE html>
    <html>
    <head>
      <!-- Comment //-->
      <meta charset="utf-8">
      <meta name="robots" content="index, follow">
      <title>An example</title>
      <link href="test.css" />
      <script>alert('of parsing..');</script>
    </head>
    <body id="body">
      <b>in a similar way to createDocumentFragment</b>
    </body>
    </html>
  </xmp>
</body>
</html>
0
Pebbl