JavaRush /Blog Java /Random-PL /Podstawy XML dla programisty Java. Część 3.2 z 3 - DOM
Ярослав
Poziom 40
Днепр

Podstawy XML dla programisty Java. Część 3.2 z 3 - DOM

Opublikowano w grupie Random-PL
<h2>Wprowadzenie</h2>Witam wszystkich czytelników artykułu, ta część jest poświęcona DOMowi. Następny będzie poświęcony JAXB i tym samym zakończy się cykl podstaw XML. Najpierw będzie trochę teorii, a potem już tylko praktyka. Zacznijmy. <h2>DOM (Document Object Model) - TEORIA</h2>Model obsługi DOM jest zaprojektowany w taki sposób, że czyta cały XML na raz i zapisuje go, tworząc hierarchię w postaci drzewa, po którym możemy łatwo się poruszać i uzyskaj dostęp do potrzebnych nam elementów. W ten sposób, mając link do górnego elementu, możemy uzyskać wszystkie linki do jego wewnętrznych elementów. Co więcej, elementy znajdujące się wewnątrz elementu są dziećmi tego elementu i jest on ich rodzicem. Kiedy już wczytamy cały plik XML do pamięci, po prostu przejdziemy przez jego strukturę i wykonamy potrzebne działania. Trochę o programistycznej części DOM w Javie: DOM ma wiele interfejsów stworzonych w celu opisywania różnych danych. Wszystkie te interfejsy dziedziczą jeden wspólny interfejs – Node. Ponieważ tak naprawdę najpopularniejszym typem danych w DOM jest Node, którym może być wszystko. Każdy węzeł ma następujące przydatne metody wyszukiwania informacji:
  1. getNodeName– uzyskaj nazwę hosta.
  2. getNodeValue– uzyskaj wartość węzła.
  3. getNodeType– pobierz typ węzła.
  4. getParentNode– pobierz węzeł, w którym znajduje się dany węzeł.
  5. getChildNodes– pobierz wszystkie węzły pochodne (węzły znajdujące się wewnątrz danego węzła).
  6. getAttributes– pobierz wszystkie atrybuty węzła.
  7. getOwnerDocument– pobierz dokument tego węzła.
  8. getFirstChild/getLastChild– pobierz pierwszy/ostatni węzeł pochodny.
  9. getLocalName– przydatne przy przetwarzaniu przestrzeni nazw w celu uzyskania nazwy bez prefiksu.
  10. getTextContent– zwraca cały tekst w elemencie i wszystkie elementy w obrębie danego elementu, łącznie z podziałami linii i spacjami.
Uwaga na temat metody 9: zawsze zwróci wartość null, chyba że użyłeś metody setNamespaceAware(true) w DocumentFactory do uruchomienia przetwarzania przestrzeni nazw. Teraz ważny szczegół: metody są wspólne dla wszystkich węzłów, ale w węźle możemy mieć zarówno element, jak i atrybut. I tu pojawiają się pytania: jaką wartość może mieć dany element? Jakie węzły pochodne może mieć atrybut? A inne nie są spójne. A wszystko jest dość proste: każda metoda będzie działać w zależności od typu węzła . Wystarczy oczywiście kierować się logiką, żeby się nie pomylić. Na przykład: jakie atrybuty mogą posiadać atrybuty? Jakie inne znaczenie ma ten element? Aby jednak nie próbować wszystkiego samodzielnie, w oficjalnej dokumentacji znajduje się bardzo przydatna tabela przedstawiająca działanie każdej metody w zależności od typu węzła: Jakość okazała się kiepska, więc tutaj jest link do dokumentacji (tabela pod adresem na górze strony): Dokumentacja węzła. Najważniejszą rzeczą do zapamiętania:
  1. TYLKO elementy mają atrybuty.
  2. Elementy NIE MAJĄ znaczenia.
  3. Nazwa węzła elementu jest taka sama jak nazwa znacznika, a nazwa węzła atrybutu jest taka sama jak nazwa atrybutu.
<h2>DOM (Document Object Model) - PRAKTYKA</h2>W części praktycznej przeanalizujemy różne rodzaje zadań związanych z wyszukiwaniem informacji w formacie XML. Aby porównać wygodę, wzięliśmy także pod uwagę dwa zadania z poprzedniego artykułu. Zacznijmy, a dobrze byłoby zacząć od importu:
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;
Zapewniam importy, żebyście nie pomylili klas :) Zadanie nr 1 - musimy pobrać informacje o wszystkich pracownikach i wyprowadzić je na konsolę z następującego pliku 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>
Jak widać wszystkie informacje mamy zapisane w elementach pracowniczych. Aby zapisać to gdzieś w naszym programie, utwórzmy klasę 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;
    }
}
Skoro mamy już opis struktury przechowywania danych, potrzebujemy kolekcji, która będzie przechowywać pracowników. Stworzymy go w samym kodzie. Musimy także utworzyć dokument w oparciu o nasz XML:
public class DOMExample {
    // Список для сотрудников из XML plik
    private static ArrayList<Employee> employees = new ArrayList<>();

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

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

        // ЗапарсLub XML, создав структуру Document. Теперь у нас есть доступ ко всем elementм, Jakим нам нужно.
        Document document = builder.parse(new File("resource/xml_file1.xml"));
    }
}
Po otrzymaniu dokumentu mamy nieograniczoną władzę nad całą strukturą pliku XML. W każdej chwili możemy pobrać dowolne elementy, wrócić do sprawdzenia dowolnych danych i ogólnie jest to bardziej elastyczne podejście niż mieliśmy w SAX. W kontekście tego zadania wystarczy wyodrębnić wszystkie elementy pracowników, a następnie wydobyć wszystkie informacje na ich temat. To całkiem proste:
public class DOMExample {
    // Список для сотрудников из XML plik
    private static ArrayList<Employee> employees = new ArrayList<>();

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

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

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

        // Получение списка всех элементов employee внутри корневого element (getDocumentElement возвращает ROOT элемент XML plik).
        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, потому нам нужно получить oznaczający атрибута с помощью метода getNodeValue()
            employees.add(new Employee(attributes.getNamedItem("name").getNodeValue(), attributes.getNamedItem("job").getNodeValue()));
        }

        // Вывод информации о каждом сотруднике
        for (Employee employee : employees)
            System.out.println(String.format("Информации о сотруднике: Nazwa - %s, должность - %s.", employee.getName(), employee.getJob()));
    }
}
Opis tego rozwiązania znajduje się bezpośrednio w rozwiązaniu. Wskazane jest, aby po zapoznaniu się z kodem wrócić do teorii i przeczytać ją ponownie. Tak naprawdę wszystko jest instynktownie jasne. Przeczytaj uważnie komentarze i nie powinno być żadnych pytań, a jeśli jakieś są, możesz napisać w komentarzach, odpowiem, lub w linku, albo po prostu uruchom swój POMYSŁ i spróbuj sam pobawić się kodem, jeśli jeszcze tego nie zrobiłeś. Zatem po uruchomieniu kodu otrzymaliśmy następujący wynik:
Информации о сотруднике: Nazwa - Maksim, должность - Middle Software Developer.
Информации о сотруднике: Nazwa - Ivan, должность - Junior Software Developer.
Информации о сотруднике: Nazwa - Franklin, должность - Junior Software Developer.
Информации о сотруднике: Nazwa - Herald, должность - Middle Software Developer.
Информации о сотруднике: Nazwa - Adam, должность - Middle Software Developer.
Информации о сотруднике: Nazwa - Leroy, должность - Junior Software Developer.
Jak widać zadanie zostało pomyślnie zakończone! Przejdźmy do kolejnego zadania :) Zadanie nr 2 - z konsoli wpisujemy nazwę elementu, o której należy wyświetlić informację o wszystkich znajdujących się w nim elementach oraz ich atrybutach z następującego pliku 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>
Wszystko jest dość proste: musimy pobrać element według jego nazwy, którą liczymy, a następnie przejść przez wszystkie węzły potomne. Aby to zrobić, musisz iterować przez wszystkie węzły podrzędne wszystkich węzłów podrzędnych, które są elementami. Rozwiązanie tego problemu:
public class DOMExample {
    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        // Ридер для считывания имени тега из консоли
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

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

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

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

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

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

        // Даже если element нет, всегда будет возвращаться список, просто он будет пустым.
        // Потому, чтобы утверждать, что element нет в файле, достаточно проверить размер списка.
        if (matchedElementsList.getLength() == 0) {
            System.out.println("Etykietka " + 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);

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

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

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

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

            // Если у данного element еще остались узлы, то вывести всю информацию про все его узлы.
            if (node.hasChildNodes())
                printInfoAboutAllChildNodes(node.getChildNodes());
        }
    }
}
Cały opis rozwiązania znajduje się w komentarzach, jednak chciałbym trochę graficznie zilustrować podejście, które zastosowaliśmy, na przykładzie z obrazka z teorii. Zakładamy, że musimy wyświetlić informację o znaczniku HTML. Jak widać, musimy przejść od góry do dołu od korzenia drzewa. Wszystkie linie są węzłami. W rozwiązaniu będziemy rekurencyjnie przechodzić od początku żądanego elementu przez wszystkie jego węzły, a jeśli jeden z jego węzłów jest elementem, to iterujemy także po wszystkich węzłach tego elementu. Zatem po uruchomieniu kodu otrzymaliśmy następujące dane wyjściowe dla elementu głównego:
Элемент был найден!
Найден элемент: oracle, его атрибуты:
Найден элемент: connection, его атрибуты:
Nazwa атрибута: value, его oznaczający: jdbc:oracle:thin:@10.220.140.48:1521:test1
Найден элемент: user, его атрибуты:
Nazwa атрибута: value, его oznaczający: secretOracleUsername
Найден элемент: password, его атрибуты:
Nazwa атрибута: value, его oznaczający: 111
Найден элемент: mysql, его атрибуты:
Найден элемент: connection, его атрибуты:
Nazwa атрибута: value, его oznaczający: jdbc:mysql:thin:@10.220.140.48:1521:test1
Найден элемент: user, его атрибуты:
Nazwa атрибута: value, его oznaczający: secretMySQLUsername
Найден элемент: password, его атрибуты:
Nazwa атрибута: value, его oznaczający: 222
Problem został pomyślnie rozwiązany! Zadanie nr 3 – z poniższego pliku XML, w którym zapisane są informacje o studentach, profesorach i pracownikach, należy je odczytać i wyprowadzić do konsoli:
<?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>
Zadanie jest dość proste, ale ciekawe. Najpierw musimy utworzyć 4 klasy: pracownik, profesor i student, a także wspólną klasę abstrakcyjną Human, aby sprowadzić nazwę zmiennej z każdej klasy do wspólnego mianownika: Abstrakcyjna klasa nadrzędna
public abstract class Human {
    private String name;

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

    public String getName() {
        return name;
    }
}
Student
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;
    }
}
Profesor
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;
    }
}
Pracownik
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;
    }
}
Teraz, gdy nasze zajęcia są już gotowe, pozostaje nam tylko napisać kod, aby uzyskać wszystkie elementy student, profesor i członek, a następnie uzyskać ich atrybuty. Do przechowywania użyjemy kolekcji, która będzie przechowywać wspólne dla wszystkich obiekty klasy nadrzędnej - Human. I tak rozwiązanie tego problemu:
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 {
        // Получение фабрики, чтобы после получить билдер dokumentов.
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

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

        // ЗапарсLub XML, создав структуру Document. Теперь у нас есть доступ ко всем elementм, Jakим нам нужно.
        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 Nazwa 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;
            }
        }
    }
}
Zauważ, że potrzebujemy tylko nazwy elementu, aby pobrać wszystkie te elementy z dokumentu. Znacząco upraszcza to proces wyszukiwania potrzebnych informacji. Wszystkie informacje dotyczące kodu znajdują się w komentarzach. Nie zastosowano niczego nowego, czego nie było w poprzednich zadaniach. Dane wyjściowe kodu:
Профессор 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
Problem rozwiązany! Zalecenia, kiedy używać DOM, a kiedy SAX. Różnica pomiędzy tymi narzędziami polega na funkcjonalności i szybkości. Jeśli potrzebujesz bardziej elastycznej funkcjonalności i możesz sobie pozwolić na marnowanie wydajności programu, twoim wyborem jest DOM, ale jeśli twoim głównym celem jest zmniejszenie kosztów pamięci, to DOM nie jest najlepszym wyborem, ponieważ odczytuje wszystkie informacje z pliku XML i przechowuje to. Dlatego metoda odczytu sekwencyjnego SAX jest tańsza. W skrócie: jeśli potrzebujesz wydajności - SAX, funkcjonalności - DOM. <h2>Wnioski</h2>Każdy programista ma swoje własne narzędzia i, w zależności od zadania, musisz użyć określonych narzędzi. W artykułach o SAX i DOM moim celem było nauczenie Cię, jak wydobywać informacje z plików XML i przetwarzać je tak, jak tego potrzebujesz. Jednak nawet jeśli przeczytałeś te artykuły, nie możesz twierdzić, że nauczyłeś się korzystać z tych narzędzi. Powinieneś poćwiczyć, przetestować kod z artykułów, zrozumieć, jak to działa i spróbować napisać coś samodzielnie. W końcu najważniejsza jest praktyka. Ostatni artykuł ukaże się w najbliższych dniach i najprawdopodobniej po zakończeniu konkursu i będzie poświęcony JAXB. JAXB to narzędzie do zapisywania obiektów w programie w formacie XML. To wszystko, mam nadzieję, że ten artykuł był przydatny i życzę powodzenia w programowaniu :) Poprzedni artykuł: [Konkurs] Podstawy XML dla programisty Java - Część 3.1 z 3 - SAX
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION