広告

Java : DocumentBuilderFactory (XML) - API使用例

DocumentBuilderFactory (Java SE 17 & JDK 17) の使用例まとめです。
だいたいのメソッドを網羅済みです。
API仕様のおともにどうぞ。


概要

アプリケーションでXMLドキュメントからDOMオブジェクト・ツリーを生成するパーサーを取得できるファクトリAPIを定義します。

クラス構成

DocumentBuilderFactoryは、XMLをDOMで操作する起点となるクラスです。

XML文字列からDOMを構築し、各要素にアクセスする一連の流れは次のようになります。

final var xml = """
        <root>
            <child-a>AAA</child-a>
            <child-b>BBB</child-b>
        </root>
        """;

final var factory = DocumentBuilderFactory.newInstance();
final var builder = factory.newDocumentBuilder();

final var document = builder.parse(new ByteArrayInputStream(xml.getBytes()));

final var childA = document.getElementsByTagName("child-a").item(0);
System.out.println(childA); // [child-a: null]
System.out.println(childA.getTextContent()); // AAA

final var childB = document.getElementsByTagName("child-b").item(0);
System.out.println(childB); // [child-b: null]
System.out.println(childB.getTextContent()); // BBB

関連記事:XML (DOM) の基本

また、XMLの処理には、

  • XML外部エンティティ(XXE)インジェクション攻撃
  • 指数関数的エンティティ展開攻撃

といった潜在的な攻撃が存在します。

XML処理は、アプリケーションを一定の脆弱性に晒すことがあります。最も顕著で有名な攻撃は、XML外部エンティティ(XXE)インジェクション攻撃と指数関数的エンティティ展開攻撃で、XML爆弾やBillion laughs攻撃とも呼ばれます。

上記の公式ドキュメントでは、XML処理のセキュリティについて詳しく解説されています。
もし信頼していないXMLを読み込む必要がある場合は、上記ドキュメントも合わせてご確認ください。


コンストラクタ

DocumentBuilderFactory ()

インスタンス化を妨げるprotectedコンストラクタです。

protectedです。
独自にサブクラスを作ることは少ないと思いますので、コード例は割愛します。

メソッド

abstract Object getAttribute (String name)

ユーザーがベースとなる実装の特定の属性を取り出すことができるようにします。

final var dtdFile = Path.of("R:", "java-work", "sample.dtd");
System.out.println(dtdFile); // R:\java-work\sample.dtd

Files.writeString(dtdFile, """
        <!ENTITY aaa "bbb">
        """);

final var xml = """
        <!DOCTYPE root SYSTEM "file:///R:/java-work/sample.dtd">
        <root>&aaa;</root>
        """;

final var factory = DocumentBuilderFactory.newInstance();

{
    // すべての外部DTDの読み込みを許可します。
    factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "all");

    final var ret = factory.getAttribute(XMLConstants.ACCESS_EXTERNAL_DTD);
    System.out.println(ret); // "all"

    final var builder = factory.newDocumentBuilder();
    final var document = builder.parse(new ByteArrayInputStream(xml.getBytes()));

    final var root = document.getDocumentElement();
    System.out.println(root); // [root: null]
    System.out.println(root.getTextContent()); // bbb
}

{
    // すべての外部DTDの読み込みを拒否します。
    factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");

    final var ret = factory.getAttribute(XMLConstants.ACCESS_EXTERNAL_DTD);
    System.out.println(ret); // ""

    final var builder = factory.newDocumentBuilder();

    try {
        final var document = builder.parse(new ByteArrayInputStream(xml.getBytes()));
    } catch (SAXException e) {
        System.out.println(e);
    }

    // 結果
    // ↓
    //org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 57;
    // 外部DTD: accessExternalDTDプロパティで設定された制限により'file'アクセスが
    // 許可されていないため、外部DTD 'sample.dtd'の読取りに失敗しました。
}

abstract boolean getFeature (String name)

名前が付けられた機能の状態を取得します。

// 指数関数的エンティティ展開攻撃の例です。
final var xml = """
        <!DOCTYPE root[
            <!ENTITY x100 "X">
            <!ENTITY x99 "&x100;&x100;">
            <!ENTITY x98 "&x99;&x99;">
            ...
            省略
            ...
            <!ENTITY x3 "&x4;&x4;">
            <!ENTITY x2 "&x3;&x3;">
            <!ENTITY x1 "&x2;&x2;">
        ]>
        <root>&x1;</root>
        """;

final var factory = DocumentBuilderFactory.newInstance();

{
    // セキュア処理機能(FSP)はデフォルトでtrueです。
    final var ret = factory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING);
    System.out.println(ret); // true

    final var builder = factory.newDocumentBuilder();

    try {
        final var document = builder.parse(new ByteArrayInputStream(xml.getBytes()));
    } catch (SAXException e) {
        System.out.println(e);
    }

    // 結果
    // ↓
    //org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 1; JAXP00010001:
    // パーサーによって、このドキュメント内で"64000"を超えるエンティティ拡張が検出されました。
    // これは、JDKによる制限です。
}

// FSPをfalseにします。
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, false);

{
    final var ret = factory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING);
    System.out.println(ret); // false

    final var builder = factory.newDocumentBuilder();

    // ※注意:エンティティは指数関数的に増えているのでパースに非常に時間がかかります。
    //final var document = builder.parse(new ByteArrayInputStream(xml.getBytes()));
}

Schema getSchema ()

setSchema(Schema schema)メソッドによって指定されたSchemaオブジェクトを取得します。

final var xsd = """
        <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
            <xsd:element name="root" type="xsd:string"/>
        </xsd:schema>
        """;

final var schemaFactory = SchemaFactory.newDefaultInstance();
final var schema = schemaFactory.newSchema(
        new StreamSource(new ByteArrayInputStream(xsd.getBytes())));

final var factory = DocumentBuilderFactory.newInstance();

System.out.println(factory.getSchema()); // null

factory.setSchema(schema);
System.out.println(factory.getSchema().equals(schema)); // true

final var errorHandler = new DefaultHandler() {
    @Override
    public void error(SAXParseException e) {
        System.out.println("-- ErrorHandler error --");
        System.out.println(e);
    }
};

{
    // スキーマで指定した文書構造と一致する例
    final var xml = """
            <root>abcd</root>
            """;

    final var builder = factory.newDocumentBuilder();
    builder.setErrorHandler(errorHandler);

    final var document = builder.parse(new ByteArrayInputStream(xml.getBytes()));

    final var root = document.getDocumentElement();
    System.out.println(root); // [root: null]
    System.out.println(root.getTextContent()); // abcd
}

{
    // スキーマで指定した文書構造と一致しない例
    final var xml = """
            <root><child>abcd</child></root>
            """;

    final var builder = factory.newDocumentBuilder();
    builder.setErrorHandler(errorHandler);

    final var document = builder.parse(new ByteArrayInputStream(xml.getBytes()));

    // 結果
    // ↓
    //-- ErrorHandler error --
    //org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 33; cvc-type.3.1.2:
    // 要素'root'は単純型であるため、要素情報アイテム[children]を含めることはできません。
}

boolean isCoalescing ()

CDATAノードをTextノードに変換し、それを隣接(存在する場合) Textノードに追加するパーサーを作成するようにファクトリが構成されているかどうかを示します。

final var xml = """
        <root>aaa<![CDATA[<&>]]></root>
        """;

final var factory = DocumentBuilderFactory.newInstance();

{
    final var ret = factory.isCoalescing();
    System.out.println(ret); // false

    final var builder = factory.newDocumentBuilder();
    final var document = builder.parse(new ByteArrayInputStream(xml.getBytes()));

    final var root = document.getDocumentElement();
    System.out.println(root); // [root: null]

    final var nodes = root.getChildNodes();
    System.out.println("-- nodes --");
    for (int i = 0; i < nodes.getLength(); i++) {
        System.out.println(nodes.item(i));
    }

    // 結果
    // ↓
    //-- nodes --
    //[#text: aaa]
    //[#cdata-section: <&>]
}

factory.setCoalescing(true);

{
    final var ret = factory.isCoalescing();
    System.out.println(ret); // true

    final var builder = factory.newDocumentBuilder();
    final var document = builder.parse(new ByteArrayInputStream(xml.getBytes()));

    final var root = document.getDocumentElement();
    System.out.println(root); // [root: null]

    final var nodes = root.getChildNodes();
    System.out.println("-- nodes --");
    for (int i = 0; i < nodes.getLength(); i++) {
        System.out.println(nodes.item(i));
    }

    // 結果
    // ↓
    //-- nodes --
    //[#text: aaa<&>]
}

boolean isExpandEntityReferences ()

エンティティ参照ノードを展開するパーサーを作成するようにファクトリが構成されているかどうかを示します。

final var xml = """
        <!DOCTYPE root [
            <!ENTITY aaa "bbb">
        ]>
        <root>&aaa;</root>
        """;

final var factory = DocumentBuilderFactory.newInstance();

{
    final var ret = factory.isExpandEntityReferences();
    System.out.println(ret); // true

    final var builder = factory.newDocumentBuilder();
    final var document = builder.parse(new ByteArrayInputStream(xml.getBytes()));

    final var root = document.getDocumentElement();
    System.out.println(root); // [root: null]

    final var child = root.getFirstChild();
    System.out.println(child); // [#text: bbb]
}

factory.setExpandEntityReferences(false);

{
    final var ret = factory.isExpandEntityReferences();
    System.out.println(ret); // false

    final var builder = factory.newDocumentBuilder();
    final var document = builder.parse(new ByteArrayInputStream(xml.getBytes()));

    final var root = document.getDocumentElement();
    System.out.println(root); // [root: null]

    if (root.getFirstChild() instanceof EntityReference entityReference) {
        System.out.println(entityReference); // [aaa: null]
    }
}

boolean isIgnoringComments ()

コメントを無視するパーサーを作成するようにファクトリが構成されているかどうかを示します。

final var xml = """
        <root>aaa<!--bbb--></root>
        """;

final var factory = DocumentBuilderFactory.newInstance();

{
    final var ret = factory.isIgnoringComments();
    System.out.println(ret); // false

    final var builder = factory.newDocumentBuilder();
    final var document = builder.parse(new ByteArrayInputStream(xml.getBytes()));

    final var root = document.getDocumentElement();
    System.out.println(root); // [root: null]

    final var nodes = root.getChildNodes();
    System.out.println("-- nodes --");
    for (int i = 0; i < nodes.getLength(); i++) {
        System.out.println(nodes.item(i));
    }

    // 結果
    // ↓
    //-- nodes --
    //[#text: aaa]
    //[#comment: bbb]
}

factory.setIgnoringComments(true);

{
    final var ret = factory.isIgnoringComments();
    System.out.println(ret); // true

    final var builder = factory.newDocumentBuilder();
    final var document = builder.parse(new ByteArrayInputStream(xml.getBytes()));

    final var root = document.getDocumentElement();
    System.out.println(root); // [root: null]

    final var nodes = root.getChildNodes();
    System.out.println("-- nodes --");
    for (int i = 0; i < nodes.getLength(); i++) {
        System.out.println(nodes.item(i));
    }

    // 結果
    // ↓
    //-- nodes --
    //[#text: aaa]
}

boolean isIgnoringElementContentWhitespace ()

要素の内容の無視できる空白を無視するパーサーを作成するようにファクトリが構成されているかどうかを示します。

無視される空白は、テキスト(#PCDATA)を持たないことを宣言した要素が対象となります。
詳細は setIgnoringElementContentWhitespace(boolean whitespace) のAPI仕様もご確認ください。

// child-a, child-b要素は半角スペースを持ちます。
final var xml = """
        <!DOCTYPE root [
            <!ELEMENT child-a (dummy?)>
            <!ELEMENT child-b (#PCDATA)>
        ]>
        <root>
            <child-a>   </child-a>
            <child-b>   </child-b>
        </root>
        """;

final var factory = DocumentBuilderFactory.newInstance();

{
    final var ret = factory.isIgnoringElementContentWhitespace();
    System.out.println(ret); // false

    final var builder = factory.newDocumentBuilder();
    final var document = builder.parse(new ByteArrayInputStream(xml.getBytes()));

    final var childA = document.getElementsByTagName("child-a").item(0);
    System.out.println(childA); // [child-a: null]

    final var childB = document.getElementsByTagName("child-b").item(0);
    System.out.println(childB); // [child-b: null]

    System.out.println(childA.getFirstChild()); // [#text:    ]
    System.out.println(childB.getFirstChild()); // [#text:    ]
}

factory.setIgnoringElementContentWhitespace(true);

{
    final var ret = factory.isIgnoringElementContentWhitespace();
    System.out.println(ret); // true

    final var builder = factory.newDocumentBuilder();
    final var document = builder.parse(new ByteArrayInputStream(xml.getBytes()));

    final var childA = document.getElementsByTagName("child-a").item(0);
    System.out.println(childA); // [child-a: null]

    final var childB = document.getElementsByTagName("child-b").item(0);
    System.out.println(childB); // [child-b: null]

    System.out.println(childA.getFirstChild()); // null
    System.out.println(childB.getFirstChild()); // [#text:    ]
}

boolean isNamespaceAware ()

ファクトリが名前空間を認識するパーサーを作成するように構成されているかどうかを示します。

final var xml = """
        <ns:root xmlns:ns="sample">
            <ns:child/>
        </ns:root>
        """;

final var factory = DocumentBuilderFactory.newInstance();

{
    final var ret = factory.isNamespaceAware();
    System.out.println(ret); // false

    final var builder = factory.newDocumentBuilder();
    final var document = builder.parse(new ByteArrayInputStream(xml.getBytes()));

    final var child = document.getElementsByTagNameNS("sample", "child").item(0);
    System.out.println(child); // null
}

factory.setNamespaceAware(true);

{
    final var ret = factory.isNamespaceAware();
    System.out.println(ret); // true

    final var builder = factory.newDocumentBuilder();
    final var document = builder.parse(new ByteArrayInputStream(xml.getBytes()));

    final var child = document.getElementsByTagNameNS("sample", "child").item(0);
    System.out.println(child); // [ns:child: null]
}

boolean isValidating ()

構文解析時にXMLコンテンツを検証するパーサーを作成するようにファクトリが構成されているかどうかを示します。

// 意図的に文書構造と一致しないXMLにしています。
final var xml = """
        <!DOCTYPE root [
            <!ELEMENT root (child-a)>
        ]>
        <root><child-z/></root>
        """;

final var factory = DocumentBuilderFactory.newInstance();

final var errorHandler = new DefaultHandler() {
    @Override
    public void error(SAXParseException e) {
        System.out.println("-- ErrorHandler error --");
        System.out.println(e);
    }
};

{
    final var ret = factory.isValidating();
    System.out.println(ret); // false

    final var builder = factory.newDocumentBuilder();
    builder.setErrorHandler(errorHandler);

    final var document = builder.parse(new ByteArrayInputStream(xml.getBytes()));

    final var childZ = document.getElementsByTagName("child-z").item(0);
    System.out.println(childZ); // [child-z: null]
}

factory.setValidating(true);

{
    final var ret = factory.isValidating();
    System.out.println(ret); // true

    final var builder = factory.newDocumentBuilder();
    builder.setErrorHandler(errorHandler);

    final var document = builder.parse(new ByteArrayInputStream(xml.getBytes()));

    // 結果
    // ↓
    //-- ErrorHandler error --
    //org.xml.sax.SAXParseException; lineNumber: 4; columnNumber: 17;
    // 要素タイプ"child-z"を宣言する必要があります。
    //-- ErrorHandler error --
    //org.xml.sax.SAXParseException; lineNumber: 4; columnNumber: 24;
    // 要素タイプ"root"のコンテンツは"(child-a)"と一致する必要があります。
}

boolean isXIncludeAware ()

XInclude処理の状態を取得します。

final var sampleFile = Path.of("R:", "java-work", "sample.xml");
System.out.println(sampleFile); // R:\java-work\sample.xml

Files.writeString(sampleFile, """
        <child>abcd</child>
        """);

final var xml = """
        <root xmlns:xi="http://www.w3.org/2001/XInclude">
            <xi:include href="file:///R:/java-work/sample.xml" parse="xml" />
        </root>
        """;

// XIncludeを使うには、名前空間も有効にする必要があります。
final var factory = DocumentBuilderFactory.newNSInstance();

{
    final var ret = factory.isXIncludeAware();
    System.out.println(ret); // false

    final var builder = factory.newDocumentBuilder();
    final var document = builder.parse(new ByteArrayInputStream(xml.getBytes()));

    final var nodes = document.getElementsByTagName("child");
    System.out.println(nodes.getLength()); // 0
}

factory.setXIncludeAware(true);

{
    final var ret = factory.isXIncludeAware();
    System.out.println(ret); // true

    final var builder = factory.newDocumentBuilder();
    final var document = builder.parse(new ByteArrayInputStream(xml.getBytes()));

    final var child = document.getElementsByTagName("child").item(0);
    System.out.println(child); // [child: null]
    System.out.println(child.getTextContent()); // abcd
}

static DocumentBuilderFactory newDefaultInstance ()

DocumentBuilderFactory組み込みシステムのデフォルト実装の新しいインスタンスを作成します。

newInstanceとほぼ同じです。
API使用例はそちらをご参照ください。

補足:newInstanceとの違い

newInstance は、いろいろと DocumentBuilderFactory の実装を探して、なければ最後に組み込みシステムのデフォルト実装を使います。 newDefaultInstance は、組み込みシステムのデフォルト実装をすぐに使います。

よって、メソッドの呼び出し速度は newDefaultInstance のほうが速いです。

自分の環境(openjdk-17.0.2_windows)ではどちらも同じ実装を返しました。

final var defaultFactory = DocumentBuilderFactory.newDefaultInstance();

// com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl@7857fe2
System.out.println(defaultFactory);

final var factory = DocumentBuilderFactory.newInstance();

// com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl@5340477f
System.out.println(factory);

どちらを使うべきか?というのは正直、分かりません…
拡張性が高いのは newInstance のほうなので、個人的には newInstance でよいのではないかなと思います。

static DocumentBuilderFactory newDefaultNSInstance ()

DocumentBuilderFactory組込みシステムのデフォルト実装の新しいNamespaceAwareインスタンスを作成します。

isNamespaceAwareがtrueとなること以外はnewDefaultInstanceと同等です。
そちらのAPI使用例もご参照ください。

final var nsFactory = DocumentBuilderFactory.newDefaultNSInstance();
System.out.println(nsFactory.isNamespaceAware()); // true

final var factory = DocumentBuilderFactory.newDefaultInstance();
System.out.println(factory.isNamespaceAware()); // false

abstract DocumentBuilder newDocumentBuilder ()

現在構成されているパラメータを使用してDocumentBuilderの新しいインスタンスを作成します。

このメソッドの使用例は、newInstance() にまとめて記載しました。
そちらのAPI使用例をご参照ください。

static DocumentBuilderFactory newInstance ()

DocumentBuilderFactoryの新しいインスタンスを取得します。

final var xml = """
        <root>
            <child-a>AAA</child-a>
            <child-b>BBB</child-b>
        </root>
        """;

final var factory = DocumentBuilderFactory.newInstance();
final var builder = factory.newDocumentBuilder();

final var document = builder.parse(new ByteArrayInputStream(xml.getBytes()));

final var childA = document.getElementsByTagName("child-a").item(0);
System.out.println(childA); // [child-a: null]
System.out.println(childA.getTextContent()); // AAA

final var childB = document.getElementsByTagName("child-b").item(0);
System.out.println(childB); // [child-b: null]
System.out.println(childB.getTextContent()); // BBB

static DocumentBuilderFactory newInstance (String factoryClassName, ClassLoader classLoader)

クラス名からDocumentBuilderFactoryの新しいインスタンスを取得します。

おそらくサードパーティ製のパーサを使うときのためのAPIです。
本記事ではコード例は割愛します。

static DocumentBuilderFactory newNSInstance ()

DocumentBuilderFactoryの新しいNamespaceAwareインスタンスを作成します。

isNamespaceAwareがtrueとなること以外はnewInstanceと同等です。
そちらのAPI使用例もご参照ください。

final var nsFactory = DocumentBuilderFactory.newNSInstance();
System.out.println(nsFactory.isNamespaceAware()); // true

final var factory = DocumentBuilderFactory.newInstance();
System.out.println(factory.isNamespaceAware()); // false

static DocumentBuilderFactory newNSInstance (String factoryClassName, ClassLoader classLoader)

クラス名からDocumentBuilderFactoryの新しいNamespaceAwareインスタンスを作成します。

おそらくサードパーティ製のパーサを使うときのためのAPIです。
本記事ではコード例は割愛します。

abstract void setAttribute (String name, Object value)

ユーザーがベースとなる実装に特定の属性を設定できるようにします。

このメソッドの使用例は、getAttribute(String name) にまとめて記載しました。
そちらのAPI使用例をご参照ください。

void setCoalescing (boolean coalescing)

このファクトリで作成されたパーサーがCDATAノードをTextノードに変換し、それを隣接(存在する場合) Textノードに追加するように指定します。

このメソッドの使用例は、isCoalescing() にまとめて記載しました。
そちらのAPI使用例をご参照ください。

void setExpandEntityReferences (boolean expandEntityRef)

このファクトリで作成されたパーサーがエンティティ参照ノードを展開するように指定します。

このメソッドの使用例は、isExpandEntityReferences() にまとめて記載しました。
そちらのAPI使用例をご参照ください。

abstract void setFeature (String name, boolean value)

このファクトリによって生成されたDocumentBuilderFactoryおよびDocumentBuilderの機能を設定します。

このメソッドの使用例は、getFeature(String name) にまとめて記載しました。
そちらのAPI使用例をご参照ください。

void setIgnoringComments (boolean ignoreComments)

このコードで作成されたパーサーがコメントを無視するように指定します。

このメソッドの使用例は、isIgnoringComments() にまとめて記載しました。
そちらのAPI使用例をご参照ください。

void setIgnoringElementContentWhitespace (boolean whitespace)

このファクトリで作成されたパーサーが、XMLドキュメントの構文解析時に要素の内容の空白(大まかに「無視できる空白」と呼ばれることがある)を排除するように指定します(XML Rec 2.10を参照)。

このメソッドの使用例は、isIgnoringElementContentWhitespace() にまとめて記載しました。
そちらのAPI使用例をご参照ください。

void setNamespaceAware (boolean awareness)

このファクトリで作成されたパーサーがXML名前空間をサポートするように指定します。

このメソッドの使用例は、isNamespaceAware() にまとめて記載しました。
そちらのAPI使用例をご参照ください。

void setSchema (Schema schema)

このファクトリから作成されたパーサーによって使用されるSchemaを設定します。

このメソッドの使用例は、getSchema() にまとめて記載しました。
そちらのAPI使用例をご参照ください。

void setValidating (boolean validating)

このファクトリで作成されたパーサーが構文解析時にドキュメントの妥当性を検証するように指定します。

このメソッドの使用例は、isValidating() にまとめて記載しました。
そちらのAPI使用例をご参照ください。

void setXIncludeAware (boolean state)

XInclude処理の状態を設定します。

このメソッドの使用例は、isXIncludeAware() にまとめて記載しました。
そちらのAPI使用例をご参照ください。


関連記事

公式ドキュメント

XML

ページの先頭へ