Programming Field

ノードをHTML(XML)ソースとして取得する

スポンサーリンク

このページでは、DOMのノードをHTML(XML)ソースとして取得する方法を紹介します。

[関連キーワード: innerHTMLnodeNamenodeValuenodeTypechildNodesattributes]

innerHTML

多くのブラウザでは、HTMLElement(またはそれに準ずる)インターフェイスに「innerHTML」プロパティが定義されており、これを利用することでその要素が持つ子要素やテキストをHTMLとして取得することができます。

[JavaScript]

var elementHTML = element.innerHTML;

ただし、innerHTMLは要素の内側のHTMLしか取得できない(「outerHTML」は外側も含みますが、実装されているブラウザはさらに限られます)こと、innerHTMLプロパティ自体が仕様書に定義されていないこと、そして通常のElementインターフェイスやNodeインターフェイスにも定義されていないため、それらの問題を解決する場合は下記の方法で代替してください。

innerHTMLouterHTML「DOM Parsing and Serialization」Elementの要素として仕様が定められており、一時はCandidate Recommendationになっていたこともあってか多くのブラウザーで実装されています。(ただしRecommendationにはなっていません。)

ツリー構造からHTML(XML)を生成する

innerHTMLを使わない場合は、NodeインターフェイスのchildNodesattributes(Elementのみ)を用いて、自分でHTML(XML)を生成する必要があります。

その際、以下の点は期待するデータに沿って微調整する必要があります。

なお、以下の例では「XML形式」でかつ、「整形しない」コードの出力となっています。ただし名前空間の接頭辞に関する処理は省略しています。

※ ブラウザーやその他XML/DOMパーサーによっては、元となるソースコードがインデントなどを用いて整形されている場合、そのテキスト文字もテキストノードとしてツリー構造に含まれる場合があります(オプションで切り替えられる場合もあります)。

子ノードを解析する際、各ノードの種類によって生成すべきソースコードが異なるため、以下ではそれぞれのノードについて見ていきます。

要素ノード

要素(Elementインターフェイス)の場合は子ノードを持つほかに属性(attributes)も持つため、それを考慮した解析を行う必要があります。

[JavaScript]

function convertElementNodeToXML(node) {
    var i;
    // 「nodeName」は「tagName」でもOK
    var ret = "<" + node.nodeName;
    if (node.attributes) {
        for (i = 0; i < node.attributes.length; i++) {
            var s = convertAttrNodeToXML(node.attributes[i]);
            // 空の文字列が返った時は追加しない
            if (s && s.length)
                ret += " " + s;
        }
    }
    if (!node.childNodes || !node.childNodes.length) {
        // 「/」の前のスペースは、実際のXMLでは不要
        ret += " />";
    } else {
        ret += ">";
        for (i = 0; i < node.childNodes.length; i++) {
            // 子要素を解析する(convertNodeToXML関数は後述)
            ret += convertNodeToXML(node.childNodes[i]);
        }
        ret += "</" + node.tagName + ">";
    }
    return ret;
}

属性(ノード)

属性ノード(Attrインターフェイス)は通常、ノードの子として現れることはないため、上記の要素ノードの解析に含めても差し支えないですが、敢えてノードとして解析する場合は以下のようになります。

なお、Attrインターフェイスが子要素を持っていない(valueのみを使用している)場合は、「=」の後ろの値に直接「value」を書きます。(ただし「< >」などをエスケープする必要があります。)

※ DOM4に限り、AttrインターフェイスはNodeインターフェイスを継承しないため、nodeNameおよびnodeValueを使用することはできません。

[JavaScript]

function convertAttrNodeToXML(node) {
    // specifiedがfalseのときは空の文字列を返す
    if (!node.specified)
        return "";
    var ret = node.name + "=\"";
    if (!node.childNodes || !node.childNodes.length) {
        // convertHTMLCharsは下記参照
        ret += convertHTMLChars(node.value);
    } else {
        for (var i = 0; i < node.childNodes.length; i++) {
            // 子要素を解析する
            ret += convertNodeToXML(node.childNodes[i]);
        }
    }
    ret += "\"";
    return ret;
}

テキストノード

テキストノード(Textインターフェイス)の場合は、

<some-tag>Text</some-tag>

の「Text」に当たる部分のみなので、出力するコードはnodeValueそのままとなります。が、テキストに「< >」などが含まれている場合はエスケープしなければなりません。

[JavaScript]

function convertHTMLChars(text) {
    if (!text)
        return text;
    return text.toString()
               .replace(/&/mg, "&amp;")
               .replace(/</mg, "&lt;")
               .replace(/>/mg, "&gt;")
               .replace(/"/mg, "&quot;");
}

function convertTextNodeToXML(node) {
    return convertHTMLChars(node.nodeValue);
}

スポンサーリンク

CDATAノード

CDATAノード(CDATASectionインターフェイス)の場合は以下のようになります。なお、テキストをエスケープする必要はありません。

※ Javaや.NETなどで、インターフェイスの型を用いてノードを判別している場合、CDATASectionインターフェイスはTextインターフェイスと認識されてしまう可能性があるので注意してください(Textインターフェイスを継承しているため)。

[JavaScript]

function convertCDATANodeToXML(node) {
    return "<![CDATA[" + node.nodeValue + "]]>";
}

実体参照ノード

※ DOM4では存在自体が廃止されています。

実体参照ノード(EntityReferenceインターフェイス)は、HTML上ではブラウザによってテキストに解析されてしまうためあまり見かけませんが、XMLを解析する場合はツリー上に現れることがあるため、「&...;」の形式に解析します。

※ パーサー側が実体参照を展開するかどうかは、仕様書には「選択できる」と書いてあるため、HTMLと同様XMLのツリー構造にもEntityReferenceが1つも現れない場合があります。

[JavaScript]

function convertEntityReferenceNodeToXML(node) {
    return "&" + node.nodeName + ";";
}

実体ノード

※ DOM4では存在自体が廃止されています。

実体ノード(Entityインターフェイス)はXML限定で、「<!ENTITY ...>」の形式に解析します。

実体ノードにはいくつか種類がありますが、それらを判定するには特定のプロパティが有効かどうかで判断します。

※ パラメータエンティティは必ず解析されてしまうため、通常はツリー構造に現れません。そのためソースに表すことが出来ません。

[JavaScript]

function convertEntityNodeToXML(node) {
    var ret = "<!ENTITY " + node.nodeName;
    if (node.publicId && node.publicId.length > 0) {
        ret += " PUBLIC \"" + node.publicId + "\"";
        if (node.systemId && node.systemId.length > 0) {
            ret += " \"" + node.systemId + "\"";
        }
    } else if (node.systemId && node.systemId.length > 0) {
        ret += " SYSTEM \"" + node.systemId + "\"";
    } else {
        ret += " \"";
        for (var i = 0; i < node.childNodes.length; i++) {
            // 子要素を解析する(convertNodeToXML関数は後述)
            ret += convertNodeToXML(node.childNodes[i]);
        }
        // 「内部一般エンティティ」にはnotationは付かない
        return ret + "\">";
    }
    if (node.notationName && node.notationName.length > 0) {
        ret += " NDATA \"" + node.notationName + "\"";
    }
    return ret + ">";
}

XML処理宣言ノード

XML処理宣言ノード(ProcessingInstructionインターフェイス)はXML限定で、「<?name ... ?>」の形式に解析します。この「name」の値はnodeNameまたはtargetプロパティに、「...」に入る値はそのままnodeValueまたはdataプロパティに入るため、簡単にソースコードに変換できます。

※ DOM4以降、Javaや.NETなどで、インターフェイスの型を用いてノードを判別している場合、ProcessingInstructionインターフェイスはTextインターフェイスを継承しているため、チェック方法によってはTextインターフェイスと認識されてしまう可能性があるので注意してください。

[JavaScript]

function convertProcessingInstructionNodeToXML(node) {
    return "<?" + node.nodeName + " " + node.nodeValue + "?>";
}

コメントノード

コメントノード(Commentインターフェイス)は、「<!-- ... -->」の「...」の部分がnodeValueプロパティに入ります。

[JavaScript]

function convertCommentNodeToXML(node) {
    return "<!--" + node.nodeValue + "-->";
}

文書ノード

文書ノード(Documentインターフェイス)はすべてのノードの親となっているノードであり、それ自身には構文が無いため、子ノードを解析した結果を結合します。

[JavaScript]

function convertDocumentNodeToXML(node) {
    var ret = "";
    if (node.childNodes && node.childNodes.length) {
        for (var i = 0; i < node.childNodes.length; i++) {
            // 子要素を解析する(convertNodeToXML関数は後述)
            ret += convertNodeToXML(node.childNodes[i]);
        }
    }
    return ret;
}

文書タイプノード

文書タイプノード(DocumentTypeインターフェイス)は「<!DOCTYPE ...>」の形式に解析します。この解析方法は「実体ノード」とほぼ同じで、notationNameが無い代わりに子ノードを解析する処理が加わります。

※ このページを書いた(2009年)時点では、IEでのみ子ノードが有効であることを確認しています。

[JavaScript]

function convertDocumentTypeNodeToXML(node) {
    var ret = "<!DOCTYPE " + node.nodeName;
    if (node.publicId && node.publicId.length > 0) {
        ret += " PUBLIC \"" + node.publicId + "\"";
        if (node.systemId && node.systemId.length > 0)
            ret += " \"" + node.systemId + "\"";
    } else if (node.systemId && node.systemId.length > 0) {
        ret += " SYSTEM \"" + node.systemId + "\"";
    }
    if (node.childNodes && node.childNodes.length > 0) {
        ret += " [";
        for (var i = 0; i < node.childNodes.length; i++) {
            // 子要素を解析する(convertNodeToXML関数は後述)
            ret += convertNodeToXML(node.childNodes[i]);
        }
        ret += "]";
    }
    return ret + ">";
}

DocumentFragmentノード

DocumentFragmentノード(DocumentFragmentインターフェイス)はツリー構造に絶対に現れないためコードは紹介しませんが、もし解析を行う場合は文書ノードと同様の処理を行うことで解析可能です。

notationノード

※ DOM4では存在自体が廃止されています。

notationノード(Notationインターフェイス)はXML限定で、「<!NOTATION ...>」の形式に解析します。

基本的には実体ノードに似た解析をしますが、notationNameプロパティが無く、必ず「PUBLIC」か「SYSTEM」が現れる代わりに「... PUBLIC pubid-literal」のみの(system-literalが無い)記述も存在します。

[JavaScript]

function convertNotationNodeToXML(node) {
    var ret = "<!NOTATION " + node.nodeName + " ";
    if (node.publicId && node.publicId.length > 0) {
        ret += "PUBLIC \"" + node.publicId + "\"";
        if (node.systemId && node.systemId.length > 0)
            ret += " \"" + node.systemId + "\"";
    } else if (node.systemId && node.systemId.length > 0) {
        ret += "SYSTEM \"" + node.systemId + "\"";
    } else {
        // 無効なnotation
        return "";
    }
    return ret + ">";
}

まとめ

これですべてのノードが解析できるため、任意のノードを受け取り、nodeTypeプロパティを用いて該当の関数を呼び出す汎用関数を作成します。

[JavaScript]

function convertNodeToXML(node) {
    if (!node)
        return node;

    switch (node.nodeType) {
        case 1:   // ELEMENT_NODE
            return convertElementNodeToXML(node);
        case 2:   // ATTRIBUTE_NODE
            return convertAttrNodeToXML(node);
        case 3:   // TEXT_NODE
            return convertTextNodeToXML(node);
        case 4:   // CDATA_SECTION_NODE
            return convertCDATANodeToXML(node);
        case 5:   // ENTITY_REFERENCE_NODE
            return convertEntityReferenceNodeToXML(node);
        case 6:   // ENTITY_NODE
            return convertEntityNodeToXML(node);
        case 7:   // PROCESSING_INSTRUCTION_NODE
            return convertProcessingInstructionNodeToXML(node);
        case 8:   // COMMENT_NODE
            return convertCommentNodeToXML(node);
        case 9:   // DOCUMENT_NODE
            return convertDocumentNodeToXML(node);
        case 10:  // DOCUMENT_TYPE_NODE
            return convertDocumentTypeNodeToXML(node);
        case 11:  // DOCUMENT_FRAGMENT_NODE
            return convertDocumentNodeToXML(node);  // 文書ノード用の関数を呼び出す
        case 12:  // NOTATION_NODE
            return convertNotationNodeToXML(node);
    }
    return null;
}

最終更新日: 2018/02/20