JavaRush /בלוג Java /Random-HE /יסודות 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 בבת אחת ושומר אותו, יוצר היררכיה בצורת עץ שדרכו נוכל לעבור בקלות וגישה לאלמנטים שאנו צריכים. לפיכך, אנו יכולים, בהינתן קישור לאלמנט העליון, לקבל את כל הקישורים לאלמנטים הפנימיים שלו. יתרה מכך, האלמנטים שנמצאים בתוך האלמנט הם הילדים של האלמנט הזה, והוא ההורה שלהם. לאחר שקראנו את כל ה-XML לזיכרון, פשוט נעבור דרך המבנה שלו ונבצע את הפעולות שאנו צריכים. קצת על חלק התכנות של ה-DOM ב-Java: ל-DOM יש ממשקים רבים שנוצרים כדי לתאר נתונים שונים. כל הממשקים הללו יורשים ממשק אחד משותף - 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: היא תמיד תחזיר null אלא אם השתמשת בשיטת setNamespaceAware(true) ב-DocumentFactory כדי להפעיל עיבוד מרחב שמות. עכשיו, פרט חשוב: השיטות משותפות לכל הצמתים, אבל ב-Node יכולים להיות לנו גם אלמנט וגם תכונה. והנה השאלות: איזה ערך יכול להיות לאלמנט? אילו צמתים נגזרים יכולה להיות לתכונה? ואחרים לא עקביים. והכל די פשוט: כל שיטה תעבוד בהתאם לסוג ה-Node . מספיק להשתמש בהיגיון, כמובן, כדי לא להתבלבל. לדוגמה: אילו תכונות יכולות להיות לתכונות? איזו משמעות נוספת יש לאלמנט? עם זאת, כדי לא לנסות הכל בעצמך, במסמכים הרשמיים יש טבלה שימושית מאוד על איך כל שיטה עובדת בהתאם לסוג ה-Node: האיכות התבררה כרעה, אז הנה קישור לתיעוד (טבלה בכתובת בראש העמוד): תיעוד צומת הדבר החשוב ביותר שיש לזכור:
  1. רק לאלמנטים יש תכונות.
  2. לאלמנטים אין משמעות.
  3. השם של צומת האלמנט זהה לשם התג, ושם צומת התכונה זהה לשם התכונה.
<h2>DOM (Document Object Model) - PRACTICE</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 הבא:
<?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.
כפי שאתה יכול לראות, המשימה הושלמה בהצלחה! נעבור למשימה הבאה :) משימה מס' 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. כפי שאתה יכול לראות, אנחנו צריכים ללכת מלמעלה למטה משורש העץ. כל הקווים הם צמתים. בפתרון, נעבור רקורסיבית מתחילת האלמנט הרצוי דרך כל הצמתים שלו, ואם אחד הצמתים שלו הוא אלמנט, אז נעבור גם דרך כל הצמתים של האלמנט הזה. אז לאחר הפעלת הקוד קיבלנו את הפלט הבא עבור אלמנט השורש:
Элемент был найден!
Найден элемент: 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 הבא, שבו נשמר מידע על סטודנטים, פרופסורים ועובדים, יש לקרוא את המידע ולהוציא אותו לקונסולה:
<?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 כיתות: עובד, פרופסור ותלמיד, וכן כיתה מופשטת משותפת אנושית על מנת להביא את משתנה השם מכל כיתה במכנה משותף: כיתת אב מופשטת
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 היא לא הבחירה הטובה ביותר, מכיוון שהוא קורא את כל המידע מקובץ ה-XML ו מאחסנת אותו. לכן, שיטת הקריאה הרציפה של SAX היא זולה יותר. בקצרה: אם אתה צריך ביצועים - SAX, פונקציונליות - DOM. <h2>מסקנה</h2>לכל מתכנת יש את הכלים שלו, ובהתאם למשימה, עליך להשתמש בכלים מסוימים. במאמרים על SAX ו-DOM, המטרה שלי הייתה ללמד אותך איך לחלץ מידע מקובצי XML ולעבד אותם כמו שאתה צריך. עם זאת, גם אם קראת את המאמרים הללו, אינך יכול לטעון שלמדת כיצד להשתמש בכלים אלה. כדאי להתאמן, לבדוק את הקוד מהמאמרים, להבין איך זה עובד ולנסות לכתוב משהו בעצמכם. אחרי הכל, הדבר החשוב ביותר הוא תרגול. הכתבה האחרונה תפורסם בימים הקרובים וככל הנראה לאחר סיום התחרות, ותוקדש ל-JAXB. JAXB הוא כלי לשמירת אובייקטים בתוכנית שלך בפורמט XML. זה הכל, אני מקווה שהמאמר הזה היה שימושי, ובהצלחה בתכנות שלך :) מאמר קודם: [תחרות] יסודות XML למתכנת Java - חלק 3.1 מתוך 3 - SAX
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION