web-dev-qa-db-ja.com

JavaScriptでドキュメントオブジェクトを作成する方法

基本的にそれが問題です。javascriptでHTMLの文字列から Document オブジェクトを動的に構築する方法を教えてください。

34
jayarjo

仕様には、DOM Core Level 2の createDocument とHTML5の createHTMLDocument の2つのメソッドが定義されています。前者は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
  • Opera 10
  • Firefox 4
  • Internet Explorer 9
  • Safari 4

興味深いことに、ActiveXObjectを使用して、古いバージョンのInternet Explorerで(種類の)HTMLドキュメントを作成できます。

var htmlDoc = new ActiveXObject("htmlfile");

結果のオブジェクトは新しいドキュメントになり、他のドキュメントと同じように操作できます。

29
Andy E

マークアップの文字列とコンテンツタイプから完全に解析されたドキュメントオブジェクトを作成しようとしていると仮定します(xmlhttprequestからhtmlを取得したため、_Content-Type_ httpヘッダー;おそらく通常_text/html_)–これは簡単なはずです:

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

ブラウザのDOMParser実装がドキュメントレンダリングと同じくらい強力で有能である理想的な未来の世界では、多分それは将来の_HTML6_標準化の取り組みにとって良い夢のような要件です。ただし、現在のブラウザにはありません。

おそらく、完全に解析されたDocumentオブジェクトを取得したいhtmlの文字列を持つという、もっと簡単な(しかしまだ厄介な)問題があるでしょう。これを行う別の方法を次に示します。これは、すべてのブラウザーで動作するはずです。まず、HTML Documentオブジェクトを作成します。

_var doc = document.implementation.createHTMLDocument('');
_

そして htmlフラグメントを入力

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

これで、docに完全に解析されたDOMがあり、alert(doc.title)を実行し、doc.querySelectorAll('p')のようなcssセレクターでスライスするか、_doc.evaluate_を使用してXPathを選択できます。

これは実際にChromeとSafari(それぞれChrome 22とSafari 6でそれぞれテストしました)などの最新のWebKitブラウザーで動作します。ページのソースコード、新しいドキュメント変数srcで再作成し、そのタイトルを読み取り、同じソースコードのHTML引用バージョンで上書きし、iframeに結果を表示します。 http:/ /codepen.io/johan/full/KLIeE

悲しいことに、私は他の現代的なブラウザがまだ非常に堅実な実装を持っているとは思わない。

22
ecmanaut

仕様( doc )に従って、次のようにdocument.implementationからアクセス可能なcreateHTMLDocumentDOMImplementationメソッドを使用できます。

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
4
Chris Baker

DOMparserが進化したため、2014年の更新された回答。これは、私が見つけることができる現在のすべてのブラウザーで動作し、上記のecManautのdocument.implementation.createHTMLDocument( '')アプローチを使用して、IEの以前のバージョンでも動作するはずです。

基本的に、IE、Opera、Firefoxはすべて「text/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
Neil F

以下はほとんどの一般的なブラウザで動作しますが、一部のブラウザでは動作しません。これは非常に単純ですshould be(しかしそうではありません):

// 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')
);
3
RobG