JavaRush /جاوا بلاگ /Random-UR /Основы XML для Java программиста. Часть 3.2 из 3 - DOM
Ярослав
سطح
Днепр

Основы XML для Java программиста. Часть 3.2 из 3 - DOM

گروپ میں شائع ہوا۔
<h2>Вступление</h2>Привет всем читателям статьи, эта часть посвящена DOM. Следующая будет посвящена JAXB и, на этом, цикл основ XML будет завершен. Сначала будет немного теории, а далее только практика. Давайте приступать. <h2>DOM (Document Object Model) - ТЕОРИЯ</h2>DOM-обработчик устроен так, что он считывает сразу весь XML и сохраняет его, создавая иерархию в виде дерева, по которой мы можем спокойно двигаться и получать доступ к нужным нам elementм. Таким образом, мы можем, имея ссылку на верхний элемент, получить все ссылки на его внутренние элементы. При чем элементы, которые внутри element – дети этого element, а он – их родитель. Однажды считав весь XML в память, мы просто будем путешествовать по его структуре и выполнять нужные нам действия. Немного уже о программной части DOM в Java: в DOM есть множество интерфейсов, которые созданы, чтобы описывать разные данные. Все эти интерфейсы наследуют один общий интерфейс – Node (узел). Потому, по сути, самый частый тип данных в DOM – это Node (узел), который может быть всем. У каждого Node есть следующие полезные методы для извлечения информации:
  1. getNodeName – получить Name узла.
  2. getNodeValue – получить meaning узла.
  3. getNodeType – получить тип узла.
  4. getParentNode – получить узел, внутри которого находится данный узел.
  5. getChildNodes – получить все производные узлы (узлы, которые внутри данного узла).
  6. getAttributes – получить все атрибуты узла.
  7. getOwnerDocument – получить document этого узла.
  8. getFirstChild/getLastChild – получить первый/последний производный узел.
  9. getLocalName – полезно при обработка пространств имён, чтобы получить Name без префикса.
  10. getTextContent – возвращает весь текст внутри element и всех элементов внутри данного element, включая переносы строчек и пробелы.
Примечание по 9 методу: он будет всегда возвращать null, если в DocumentFactory вы не воспользовались методом setNamespaceAware(true), чтобы запустить обработку пространств имён. Теперь, важная деталь: методы общие для всех Node, но в Node у нас может быть How элемент, так и атрибут. И тут вопросы: Howое meaning может быть у element? Какие производные узлы могут быть у атрибута? И другие не состыковки. А все довольно просто: каждый метод будет работать в зависимости от типа Node. Достаточно использовать логику, конечно, чтобы не запутаться. Например: Howие атрибуты способны быть у атрибутов? Какое еще meaning у element? Однако, чтобы самому не пробовать все, в официальных доках есть очень полезная табличка по работе каждого метода в зависимости от типа Node: Качество плохое получилось, так что link на documentацию (table вверху pages): Документация Node Самое важное, что нужно запомнить:
  1. Атрибуты есть ТОЛЬКО у элементов.
  2. У элементов НЕТ значения.
  3. Name узла-element совпадает с именем тега, а узла-атрибута с именем атрибута.
<h2>DOM (Document Object Model) – ПРАКТИКА</h2>В практической части мы будем разбирать разного рода задачки по поиску информации в XML. Так же взяты две задачи из прошлой статьи для сравнения удобства. Давайте начинать, а начать хорошо было бы с импортов:

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;
Даю импорты, чтобы вы не спутали классы :) Задача №1 – нам нужно достать информацию о всех сотрудниках и вывести её в консоль из следующего XML file:

<?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>
Как мы можем видеть, у нас вся информация сохранена в elementх employee. Для того, чтобы где-то хранить её у нас в программе, давайте создадим класс 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;
    }
}
Теперь, когда у нас есть описание структуры для хранения данных, нам нужна коллекция, которая будет хранить сотрудников. Её мы будем создавать в самом codeе. Так же нам нужно создать Document на основе нашего 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")); 
    }
}
После получения documentа, мы обладаем неограниченной властью над всей структурой XML file. Мы можем получать любые элементы в любое время, возвращаться обратно, чтобы проверить Howие-либо данные и, в целом, более гибкий подход, чем был у нас в SAX. В контексте данной задачи, нам нужно просто извлечь все элементы employee, а после извлечь всю информацию про них. Это достаточно просто:

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()));
    }
}
Описание данного решения прямо в решении. Желательно после просмотра codeа, вернуться обратно к теории и прочитать еще раз. На самом деле, все понятно инстинктивно. Прочитайте внимательно комментарии и вопросов быть не должно, а если остались – можете написать в комментариях, я отвечу, or в лычку, or просто запустить свою ИДЕЮ и самому попробовать поиграться с codeом, если вы еще этого не сделали. Таким образом, после запуска codeа мы получor следующие выходные данные:

Информации о сотруднике: 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.
Как можно заметить, задача успешно выполнена! Давайте переходить к следующей задаче :) Задача №2 – вводится с консоли Name element, про который нужно вывести информацию об всех elementх внутри его и их атрибутах со следующего XML file:

<?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>
Все достаточно просто: мы должны получить элемент по его имени, которое считаем, а после пройти по всем дочерним узлам. Для этого нужно пройтись по всем дочерним узлам всех дочерних узлов, которые являются elementми. Решение данной задачи:

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. Как вы видите, нам нужно идти сверху-вниз от корня дерева. Все линии – это узлы. В решении мы рекурсивно будем идти от начала нужного element по всем его узлам, и если Howой-то из его узлов – элемент, то мы перебираем так же все узлы этого element. Таким образом, после запуска codeа мы получor следующие выходные данные для element root:

Элемент был найден!
Найден элемент: 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
Задача успешно решена! Задача №3 – из следующего XML file, где сохранена информация про студентов, профессоров и сотрудников, нужно считать информацию и вывести её в консоль:

<?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, чтобы вынести переменную name из каждого класса под общий знаменатель: Абстрактный класс-родитель

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;
    }
}
Теперь, когда наши классы готовы, нам достаточно написать code для получения всех элементов student, professor и member, а потом достать их атрибуты. Для хранения мы будем использовать коллекцию, которая будет хранить an objectы общего для всех родительского класса – 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;
            }
        }
    }
}
Заметьте, что нам достаточно только названия element, чтобы получить вообще все эти элементы из documentа. Это значительно упрощает процесс поиска нужной информации. Вся информация про code помещена в комментарии. Нового ничего не было использовано, чего не было бы в предыдущих задачах. Выходные данные codeа:

Профессор 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 Difference между данными инструментами в функциональности и скорости работы. Если вам нужен более гибкий функционал и вы можете позволить себе тратить производительность программы, то ваш выбор – DOM, если же ваша главная цель – сокращение затрат памяти, то DOM не лучший выбор, так How он считывает всю информацию из XML file и сохраняет её. Потому, метод SAX последовательного считывания менее затратный. Коротко: если нужна производительность – SAX, функциональность – DOM. <h2>Заключение</h2>У каждого программиста есть свои инструменты, и, в зависимости от задачи, нужно использовать те or иные инструменты. В статьях про SAX и DOM я ставил целью обучить вас извлекать информацию из XML файлов и обрабатывать их так, How вам это нужно. Однако, даже если вы прочитали данные статьи, вы не можете утверждать, что обучorсь использовать эти инструменты. Вы должны попрактиковаться, протестировать code из статей, разобраться в его работе и попробовать самим что-нибудь написать. Как ниHow, самое важное – практика. Последняя статья будет в ближайшие дни и, видимо, уже по окончанию срока конкурса, и будет посвящена JAXB. JAXB – инструмент для сохранения an objectов в вашей программе в формат XML. На этом все, надеюсь, что эта статья была полезна, и успехов вам в программировании :) Предыдущая статья: [Конкурс] Основы XML для Java программиста - Часть 3.1 из 3 - SAX
تبصرے
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION