Как создавать объекты Document с помощью JavaScript - PullRequest
33 голосов
/ 22 ноября 2011

По сути, это вопрос, как можно динамически построить объект Document из строки HTML в javascript?

Ответы [ 5 ]

29 голосов
/ 22 ноября 2011

Существует два метода, определенных в спецификациях: createDocument из DOM Core Level 2 и createHTMLDocument из HTML5 Первый создает документ XML (включая XHTML), второй создает документ HTML. Оба находятся в качестве функций на интерфейсе DOMImplementation.

var impl    = document.implementation,
    xmlDoc  = impl.createDocument(namespaceURI, qualifiedNameStr, documentType),
    htmlDoc = impl.createHTMLDocument(title);

На самом деле эти методы довольно молоды и реализованы только в последних версиях браузера. Согласно http://quirksmode.org и MDN , следующие браузеры поддерживают createHTMLDocument:

  • Chrome 4
  • Опера 10
  • Firefox 4
  • Internet Explorer 9
  • Safari 4

Интересно, что вы можете (вроде) создавать HTML-документ в более старых версиях Internet Explorer, используя ActiveXObject:

var htmlDoc = new ActiveXObject("htmlfile");

Полученный объект будет новым документом, которым можно манипулировать, как и любым другим документом.

21 голосов
/ 05 ноября 2012

Предполагается, что вы пытаетесь создать полностью проанализированный объект Document из строки разметки и типа контента, о котором вы также знаете (возможно, потому, что вы получили html из xmlhttprequest и, таким образом, получили тип контента в его Content-Type http header; вероятно, обычно text/html) - это должно быть просто:

var doc = (new DOMParser).parseFromString(markup, mime_type);

в идеальном будущем мире, где реализации браузеров DOMParser столь же сильны и компетентны, как и их рендеринг документов - возможно, это хорошее требование для будущих усилий по стандартам HTML6. Оказывается, ни один из нынешних браузеров этого не делает.

Возможно, у вас есть более простая (но все еще грязная) проблема с наличием строки html, для которой вы хотите получить полностью разобранный Document объект. Вот еще один способ сделать это, который также должен работать во всех браузерах - сначала вы создаете объект HTML Document:

var doc = document.implementation.createHTMLDocument('');

, а затем заполнить его HTML-фрагментом :

doc.open();
doc.write(html);
doc.close();

Теперь у вас должен быть полностью разобранный DOM в doc, который вы можете запустить на alert(doc.title), срезать с помощью селекторов css, таких как doc.querySelectorAll('p') или то же XPath, используя doc.evaluate.

Это на самом деле работает в современных браузерах WebKit, таких как Chrome и Safari (я только что протестировал в Chrome 22 и Safari 6 соответственно) - вот пример, который берет исходный код текущей страницы, воссоздает его в новой переменной документа src, считывает его заголовок, переписывает его HTML-цитированной версией того же исходного кода и показывает результат в iframe: http://codepen.io/johan/full/KLIeE

К сожалению, я не думаю, что какие-либо другие современные браузеры имеют столь же надежные реализации.

4 голосов
/ 27 сентября 2014

Обновленный ответ за 2014 год по мере развития DOMparser.Это работает во всех текущих браузерах, которые я могу найти, и должно работать также в более ранних версиях IE, используя описанный выше ecManaut document.implementation.createHTMLDocument ('').

По сути, IE, Opera, Firefox могут все анализироватькак "текст / HTML".Safari анализирует как «text / xml».

Однако остерегайтесь нетерпимого анализа XML.Анализ Safari будет разбиваться на неразрывные пробелы и другие символы HTML (французские / немецкие акценты), обозначенные амперсандами.Вместо того чтобы обрабатывать каждый символ отдельно, приведенный ниже код заменяет все амперсанды бессмысленной символьной строкой «j! J!».Впоследствии эту строку можно повторно отобразить как амперсанд при отображении результатов в браузере (я обнаружил, что проще, чем пытаться обрабатывать амперсанды при «ложном» разборе 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);
}
}
4 голосов
/ 22 ноября 2011

Согласно спецификации ( doc ), можно использовать метод createHTMLDocument из DOMImplementation, доступный через document.implementation следующим образом:

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
3 голосов
/ 29 августа 2012

Следующее работает в большинстве распространенных браузеров, но не в некоторых.Вот как просто должно быть (но не так):

// 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);

Для учета капризов пользовательских агентов может быть лучше следующее (обратите внимание на указание):

/*
 * 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);

Не откладывайте на количество кода, комментариев много, его можно немного сократить, но он станет менее читабельным.

О, и если разметкадействительный XML, жизнь намного проще:

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')
);
...