JavaRush /Java Blog /Random-JA /Java プログラマのための XML の基礎。パート 3.2/3 - DOM
Ярослав
レベル 40
Днепр

Java プログラマのための XML の基礎。パート 3.2/3 - DOM

Random-JA グループに公開済み
<h2>はじめに</h2>この記事を読んでいる皆さん、こんにちは。このパートは DOM について説明します。次は JAXB について説明します。これで XML の基礎のサイクルが完了します。最初に少し理論を説明してから、あとは実践するだけです。始めましょう。 <h2>DOM (ドキュメント オブジェクト モデル) - 理論</h2>DOM ハンドラーは、すべての XML を一度に読み取って保存し、簡単に移動できるツリー形式の階層を作成するように設計されています。そして必要な要素にアクセスします。 したがって、最上位要素へのリンクを指定すると、その内部要素へのすべてのリンクを取得できます。さらに、要素内にある要素はこの要素の子であり、この要素はその親になります。すべての XML をメモリに読み込んだら、その構造をたどって必要なアクションを実行するだけです。Java の DOM のプログラミング部分について少し説明します。DOM には、さまざまなデータを記述するために作成された多くのインターフェイスがあります。これらすべてのインターフェイスは、1 つの共通インターフェイスである Node を継承します。実際、DOM で最も一般的なデータ型は Node であり、何でもかまいません。 各ノードには、情報を取得するための次の便利なメソッドがあります。
  1. getNodeName– ホスト名を取得します。
  2. getNodeValue– ノード値を取得します。
  3. getNodeType– ノードタイプを取得します。
  4. getParentNode– 指定されたノードが存在するノードを取得します。
  5. getChildNodes– すべての派生ノード (特定のノード内にあるノード) を取得します。
  6. getAttributes– すべてのノード属性を取得します。
  7. getOwnerDocument– このノードのドキュメントを取得します。
  8. getFirstChild/getLastChild– 最初/最後の派生ノードを取得します。
  9. getLocalName– 名前空間を処理してプレフィックスのない名前を取得する場合に便利です。
  10. getTextContent– 要素内のすべてのテキストと、改行やスペースを含む指定された要素内のすべての要素を返します。
メソッド 9 に関する注意: DocumentFactory で setNamespaceAware(true) メソッドを使用してネームスペース処理をトリガーしない限り、このメソッドは常に null を返します。 ここで、重要な詳細: メソッドはすべてのノードに共通ですが、ノードでは要素と属性の両方を持つことができます。ここで疑問が生じます: 要素はどのような値を持つことができるのでしょうか? 属性にはどのような派生ノードを含めることができますか? また、他のものは一貫性がありません。そしてすべては非常に単純です。各メソッドはノードのタイプに応じて機能します。もちろん、混乱しないように論理を使用するだけで十分です。たとえば、属性はどのような属性を持つことができますか? この要素には他にどのような意味があるのでしょうか? ただし、すべてを自分で試さないように、公式ドキュメントには、ノードの種類に応じて各メソッドがどのように機能するかについての非常に便利な表があります。 品質が悪いことが判明したため、ドキュメントへのリンクをここに示します (表は次のとおりです)。ページの上部):ノードのドキュメント 覚えておくべき最も重要なこと:
  1. 要素のみが属性を持ちます。
  2. 要素には意味がありません。
  3. 要素ノードの名前はタグの名前と同じであり、属性ノードの名前は属性の名前と同じです。
<h2>DOM (ドキュメント オブジェクト モデル) - 実践</h2>実践部分では、XML 内の情報を検索するさまざまな種類のタスクを分析します。また、前回の記事から 2 つのタスクを取り上げて、利便性を比較しました。始めましょう。インポートから始めるとよいでしょう。
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.IOException;
クラスを混同しないようにインポートを提供します :) タスク No. 1 - すべての従業員に関する情報を取得し、次の XML ファイルからコンソールに出力する必要があります。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<company>
    <name>IT-Heaven</name>
    <offices>
        <office floor="1" room="1">
            <employees>
                <employee name="Maksim" job="Middle Software Developer" />
                <employee name="Ivan" job="Junior Software Developer" />
                <employee name="Franklin" job="Junior Software Developer" />
            </employees>
        </office>
        <office floor="1" room="2">
            <employees>
                <employee name="Herald" job="Middle Software Developer" />
                <employee name="Adam" job="Middle Software Developer" />
                <employee name="Leroy" job="Junior Software Developer" />
            </employees>
        </office>
    </offices>
</company>
ご覧のとおり、すべての情報が従業員要素に保存されています。それをプログラム内のどこかに保存するために、クラスを作成しましょうEmployee
public class Employee {
    private String name, job;

    public Employee(String name, String job) {
        this.name = name;
        this.job = job;
    }

    public String getName() {
        return name;
    }

    public String getJob() {
        return job;
    }
}
データを保存するための構造を説明したので、従業員を保存するコレクションが必要です。コード自体で作成します。XML に基づいてドキュメントを作成する必要もあります。
public class DOMExample {
    // Список для сотрудников из XML file
    private static ArrayList<Employee> employees = new ArrayList<>();

    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        // Получение фабрики, чтобы после получить билдер documentов.
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

        // Получor из фабрики билдер, который парсит XML, создает структуру Document в виде иерархического дерева.
        DocumentBuilder builder = factory.newDocumentBuilder();

        // Запарсor XML, создав структуру Document. Теперь у нас есть доступ ко всем elementм, Howим нам нужно.
        Document document = builder.parse(new File("resource/xml_file1.xml"));
    }
}
ドキュメントを受信すると、XML ファイルの構造全体に対して無制限の権限が与えられます。いつでも要素をフェッチでき、戻ってデータを確認できるため、一般に SAX よりも柔軟なアプローチが可能です。このタスクのコンテキストでは、すべての従業員要素を抽出し、それらに関するすべての情報を抽出するだけです。それは非常に簡単です:
public class DOMExample {
    // Список для сотрудников из XML file
    private static ArrayList<Employee> employees = new ArrayList<>();

    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        // Получение фабрики, чтобы после получить билдер documentов.
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

        // Получor из фабрики билдер, который парсит XML, создает структуру Document в виде иерархического дерева.
        DocumentBuilder builder = factory.newDocumentBuilder();

        // Запарсor XML, создав структуру Document. Теперь у нас есть доступ ко всем elementм, Howим нам нужно.
        Document document = builder.parse(new File("resource/xml_file1.xml"));

        // Получение списка всех элементов employee внутри корневого element (getDocumentElement возвращает ROOT элемент XML file).
        NodeList employeeElements = document.getDocumentElement().getElementsByTagName("employee");

        // Перебор всех элементов employee
        for (int i = 0; i < employeeElements.getLength(); i++) {
            Node employee = employeeElements.item(i);

            // Получение атрибутов каждого element
            NamedNodeMap attributes = employee.getAttributes();

            // Добавление сотрудника. Атрибут - тоже Node, потому нам нужно получить meaning атрибута с помощью метода getNodeValue()
            employees.add(new Employee(attributes.getNamedItem("name").getNodeValue(), attributes.getNamedItem("job").getNodeValue()));
        }

        // Вывод информации о каждом сотруднике
        for (Employee employee : employees)
            System.out.println(String.format("Информации о сотруднике: Name - %s, должность - %s.", employee.getName(), employee.getJob()));
    }
}
このソリューションの説明はソリューション内にあります。コードを見た後、理論に戻ってもう一度読むことをお勧めします。実際、すべては直感的に明らかです。コメントを注意深く読んでください。質問はありません。質問がある場合は、コメントに書いてください。私が回答します。またはリンクに記入するか、IDEA を実行して自分でコードを試してみてください。あなたはまだそうしていません。 コードを実行すると、次の出力が得られました。
Информации о сотруднике: Name - Maksim, должность - Middle Software Developer.
Информации о сотруднике: Name - Ivan, должность - Junior Software Developer.
Информации о сотруднике: Name - Franklin, должность - Junior Software Developer.
Информации о сотруднике: Name - Herald, должность - Middle Software Developer.
Информации о сотруднике: Name - Adam, должность - Middle Software Developer.
Информации о сотруднике: Name - Leroy, должность - Junior Software Developer.
ご覧のとおり、タスクは正常に完了しました。次のタスクに進みましょう :) タスク No. 2 - 要素の名前をコンソールから入力します。これについては、次の XML ファイルからその要素内のすべての要素とその属性に関する情報を表示する必要があります。
<?xml version="1.0" encoding="UTF-8"?>
<root>
    <oracle>
        <connection value="jdbc:oracle:thin:@10.220.140.48:1521:test1" />
        <user value="secretOracleUsername" />
        <password value="111" />
    </oracle>

    <mysql>
        <connection value="jdbc:mysql:thin:@10.220.140.48:1521:test1" />
        <user value="secretMySQLUsername" />
        <password value="222" />
    </mysql>
</root>
すべては非常に単純です。名前を数えて要素を取得し、すべての子ノードを調べます。これを行うには、要素であるすべての子ノードのすべての子ノードを反復処理する必要があります。 この問題の解決策:
public class DOMExample {
    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        // Ридер для считывания имени тега из консоли
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

        // Получение фабрики, чтобы после получить билдер documentов.
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

        // Получor из фабрики билдер, который парсит XML, создает структуру Document в виде иерархического дерева.
        DocumentBuilder builder = factory.newDocumentBuilder();

        // Запарсor XML, создав структуру Document. Теперь у нас есть доступ ко всем elementм, Howим нам нужно.
        Document document = builder.parse(new File("resource/xml_file3.xml"));

        // Считывание имени тега для поиска его в файле
        String element = reader.readLine();

        // Получение списка элементов, однако для удобства будем рассматривать только первое совпадение в documentе.
        // Так же заметьте, что мы ищем элемент внутри documentа, а не рут element. Это сделано для того, чтобы рут элемент тоже искался.
        NodeList matchedElementsList = document.getElementsByTagName(element);

        // Даже если element нет, всегда будет возвращаться список, просто он будет пустым.
        // Потому, чтобы утверждать, что element нет в файле, достаточно проверить размер списка.
        if (matchedElementsList.getLength() == 0) {
            System.out.println("Tag " + element + " не был найден в файле.");
        } else {
            // Получение первого element.
            Node foundedElement = matchedElementsList.item(0);

            System.out.println("Элемент был найден!");

            // Если есть данные внутри, вызов метода для вывода всей информации
            if (foundedElement.hasChildNodes())
                printInfoAboutAllChildNodes(foundedElement.getChildNodes());
        }
    }

    /**
     * Рекурсивный метод, который будет выводить информацию про все узлы внутри всех узлов, которые пришли параметром, пока не будут перебраны все узлы.
     * @param list Список узлов.
     */
    private static void printInfoAboutAllChildNodes(NodeList list) {
        for (int i = 0; i < list.getLength(); i++) {
            Node node = list.item(i);

            // У элементов есть два вида узлов - другие элементы or текстовая информация. Потому нужно разбираться две ситуации отдельно.
            if (node.getNodeType() == Node.TEXT_NODE) {
                // Фильтрация информации, так How пробелы и переносы строчек нам не нужны. Это не информация.
                String textInformation = node.getNodeValue().replace("\n", "").trim();

                if(!textInformation.isEmpty())
                    System.out.println("Внутри element найден текст: " + node.getNodeValue());
            }
            // Если это не текст, а элемент, то обрабатываем его How элемент.
            else {
                System.out.println("Найден элемент: " + node.getNodeName() + ", его атрибуты:");

                // Получение атрибутов
                NamedNodeMap attributes = node.getAttributes();

                // Вывод информации про все атрибуты
                for (int k = 0; k < attributes.getLength(); k++)
                    System.out.println("Name атрибута: " + attributes.item(k).getNodeName() + ", его meaning: " + attributes.item(k).getNodeValue());
            }

            // Если у данного element еще остались узлы, то вывести всю информацию про все его узлы.
            if (node.hasChildNodes())
                printInfoAboutAllChildNodes(node.getChildNodes());
        }
    }
}
解決策の全説明はコメントにありますが、理論の図の例を使用して、使用したアプローチを少し図的に説明したいと思います。 html タグに関する情報を表示する必要があると仮定します。ご覧のとおり、ツリーの根元から上から下に移動する必要があります。すべての行はノードです。 この解決策では、目的の要素の先頭からそのすべてのノードを再帰的に移動し、そのノードの 1 つが要素である場合は、この要素のすべてのノードも反復します。 コードを実行すると、ルート要素に対して次の出力が得られました。
Элемент был найден!
Найден элемент: oracle, его атрибуты:
Найден элемент: connection, его атрибуты:
Name атрибута: value, его meaning: jdbc:oracle:thin:@10.220.140.48:1521:test1
Найден элемент: user, его атрибуты:
Name атрибута: value, его meaning: secretOracleUsername
Найден элемент: password, его атрибуты:
Name атрибута: value, его meaning: 111
Найден элемент: mysql, его атрибуты:
Найден элемент: connection, его атрибуты:
Name атрибута: value, его meaning: jdbc:mysql:thin:@10.220.140.48:1521:test1
Найден элемент: user, его атрибуты:
Name атрибута: value, его meaning: secretMySQLUsername
Найден элемент: password, его атрибуты:
Name атрибута: value, его meaning: 222
問題は無事に解決されました! タスク No. 3 – 学生、教授、従業員に関する情報が保存されている次の XML ファイルから、情報を読み取り、コンソールに出力する必要があります。
<?xml version="1.0" encoding="UTF-8"?>
<database>
    <students>
        <student name="Maksim" course="3" specialization="CE" />
        <student name="Stephan" course="1" specialization="CS" />
        <student name="Irvin" course="2" specialization="CE" />
    </students>

    <professors>
        <professor name="Herald" experience="7 years in University" discipline="Math" />
        <professor name="Adam" experience="4 years in University" discipline="Programming" />
        <professor name="Anton" experience="6 years in University" discipline="English" />
    </professors>

    <service>
        <member name="John" position="janitor" />
        <member name="Jordan" position="janitor" />
        <member name="Mike" position="janitor" />
    </service>
</database>
タスクは非常に単純ですが、興味深いものです。まず、従業員、教授、学生の 4 つのクラスと、共通の抽象クラス Human を作成して、各クラスの名前変数を共通の分母の下に置く必要があります: 抽象親クラス
public abstract class Human {
    private String name;

    public Human(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}
学生
public class Student extends Human {
    private String course, specialization;

    public Student(String name, String course, String specialization) {
        super(name);
        this.course = course;
        this.specialization = specialization;
    }

    public String getCourse() {
        return course;
    }

    public String getSpecialization() {
        return specialization;
    }

    public String toString() {
        return "Голодный студент " + getName() + " " + course + "-го курса, обучающийся по специальности " + specialization;
    }
}
教授
public class Professor extends Human {
    private String experience, discipline;

    public Professor(String name, String experience, String discipline) {
        super(name);
        this.experience = experience;
        this.discipline = discipline;
    }

    public String getExperience() {
        return experience;
    }

    public String getDiscipline() {
        return discipline;
    }

    public String toString() {
        return "Профессор " + getName() + ", обладающий опытом: \"" + experience + "\", выкладает дисциплину " + discipline;
    }
}
従業員
public class Member extends Human {
    private String position;

    public Member(String name, String position) {
        super(name);
        this.position = position;
    }

    public String getPosition() {
        return position;
    }

    public String toString() {
        return "Сотрудник обслуживающего персонала " + getName() + ", должность: " + position;
    }
}
クラスの準備ができたので、学生、教授、メンバーのすべての要素を取得し、それらの属性を取得するコードを記述するだけです。ストレージには、すべてに共通の親クラスである Human のオブジェクトを格納するコレクションを使用します。 したがって、この問題の解決策は次のとおりです。
public class DOMExample {
    // Коллекция для хранения всех людей
    private static ArrayList<Human> humans = new ArrayList<>();

    // Константы для элементов
    private static final String PROFESSOR = "professor";
    private static final String MEMBER = "member";
    private static final String STUDENT = "student";

    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        // Получение фабрики, чтобы после получить билдер documentов.
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

        // Получor из фабрики билдер, который парсит XML, создает структуру Document в виде иерархического дерева.
        DocumentBuilder builder = factory.newDocumentBuilder();

        // Запарсor XML, создав структуру Document. Теперь у нас есть доступ ко всем elementм, Howим нам нужно.
        Document document = builder.parse(new File("resource/xml_file3.xml"));

        // Получение информации про каждый элемент отдельно
        collectInformation(document, PROFESSOR);
        collectInformation(document, MEMBER);
        collectInformation(document, STUDENT);

        // Вывод информации
        humans.forEach(System.out::println);
    }

    /**
     * Метод ищет информацию про теги по имени element и вносит всю информацию в коллекцию humans.
     * @param document Документ, в котором будем искать элементы.
     * @param element Name element, теги которого нужно найти. Должна быть одна из констант, которые определяются выше.
     */
    private static void collectInformation(Document document, final String element) {
        // Получение всех элементов по имени тега.
        NodeList elements = document.getElementsByTagName(element);

        // Перебор всех найденных элементов
        for (int i = 0; i < elements.getLength(); i++) {
            // Получение всех атрибутов element
            NamedNodeMap attributes = elements.item(i).getAttributes();
            String name = attributes.getNamedItem("name").getNodeValue();

            // В зависимости от типа element, нам нужно собрать свою дополнительну информацию про каждый подкласс, а после добавить нужные образцы в коллекцию.
            switch (element) {
                case PROFESSOR: {
                    String experience = attributes.getNamedItem("experience").getNodeValue();
                    String discipline = attributes.getNamedItem("discipline").getNodeValue();

                    humans.add(new Professor(name, experience, discipline));
                } break;
                case STUDENT: {
                    String course = attributes.getNamedItem("course").getNodeValue();
                    String specialization = attributes.getNamedItem("specialization").getNodeValue();

                    humans.add(new Student(name, course, specialization));
                } break;
                case MEMBER: {
                    String position = attributes.getNamedItem("position").getNodeValue();

                    humans.add(new Member(name, position));
                } break;
            }
        }
    }
}
ドキュメントからこれらすべての要素を取得するには、要素名のみが必要であることに注意してください。これにより、必要な情報を見つけるプロセスが大幅に簡素化されます。コードに関するすべての情報はコメントに含まれています。以前のタスクに存在しなかった新しいものは何も使用されていません。 コード出力:
Профессор Herald, обладающий опытом: "7 years in University", выкладает дисциплину Math
Профессор Adam, обладающий опытом: "4 years in University", выкладает дисциплину Programming
Профессор Anton, обладающий опытом: "6 years in University", выкладает дисциплину English
Сотрудник обслуживающего персонала John, должность: janitor
Сотрудник обслуживающего персонала Jordan, должность: janitor
Сотрудник обслуживающего персонала Mike, должность: janitor
Голодный студент Maksim 3-го курса, обучающийся по специальности CE
Голодный студент Stephan 1-го курса, обучающийся по специальности CS
Голодный студент Irvin 2-го курса, обучающийся по специальности CE
問題が解決しました! DOM を使用する場合と SAX を使用する場合の推奨事項 これらのツールの違いは、機能と速度にあります。より柔軟な機能が必要で、プログラムのパフォーマンスを無駄にする余裕がある場合は、DOM を選択します。ただし、主な目的がメモリ コストを削減することである場合、DOM は最良の選択ではありません。DOM は、XML ファイルからすべての情報を読み取り、それを保管します。したがって、SAX シーケンシャル読み取り方式の方が安価です。 簡単に言うと、パフォーマンスが必要な場合は SAX、機能性が必要な場合は DOM です。 <h2>結論</h2>各プログラマは独自のツールを持っており、タスクに応じて特定のツールを使用する必要があります。SAX と DOM に関する記事での私の目標は、XML ファイルから情報を抽出し、必要な方法で処理する方法を説明することでした。ただし、これらの記事を読んだとしても、これらのツールの使用方法を学んだとは言えません。記事のコードを練習してテストし、コードがどのように機能するかを理解し、自分で何かを書いてみるべきです。結局のところ、最も重要なことは練習です。最後の記事は数日以内に、どうやらコンテスト終了後に公開される予定で、JAXB について取り上げられる予定です。JAXB は、プログラム内のオブジェクトを XML 形式で保存するためのツールです。以上です。この記事がお役に立てば幸いです。そして、あなたのプログラミングが頑張ってください :) 前の記事: [コンテスト] Java プログラマーのための XML の基礎 - パート 3.1/3 - SAX
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION