JavaRush /Blog Java /Random-VI /Khái niệm cơ bản về XML dành cho lập trình viên Java. Phầ...
Ярослав
Mức độ
Днепр

Khái niệm cơ bản về XML dành cho lập trình viên Java. Phần 3.2/3 - DOM

Xuất bản trong nhóm
<h2>Giới thiệu</h2>Xin chào tất cả các độc giả của bài viết, phần này dành riêng cho DOM. Phần tiếp theo sẽ được dành cho JAXB và cùng với đó, chu trình cơ bản về XML sẽ được hoàn thành. Đầu tiên sẽ có một chút lý thuyết, sau đó chỉ có thực hành. Bắt đầu nào. <h2>DOM (Mô hình đối tượng tài liệu) - LÝ THUYẾT</h2>Trình xử lý DOM được thiết kế theo cách nó đọc tất cả XML cùng một lúc và lưu nó, tạo ra một hệ thống phân cấp dưới dạng cây mà chúng ta có thể dễ dàng di chuyển qua đó và truy cập các yếu tố chúng tôi cần. Vì vậy, chúng ta có thể, với một liên kết đến phần tử trên cùng, nhận được tất cả các liên kết đến các phần tử bên trong của nó. Hơn nữa, các phần tử bên trong phần tử là phần tử con của phần tử này và nó là phần tử mẹ của chúng. Khi đã đọc tất cả XML vào bộ nhớ, chúng ta sẽ chỉ cần duyệt qua cấu trúc của nó và thực hiện các hành động mà chúng ta cần. Nói một chút về phần lập trình của DOM trong Java: DOM có nhiều giao diện được tạo ra để mô tả các dữ liệu khác nhau. Tất cả các giao diện này đều kế thừa một giao diện chung - Node. Bởi vì trên thực tế, loại dữ liệu phổ biến nhất trong DOM là Node, có thể là bất kỳ loại dữ liệu nào. Mỗi Nút có các phương thức hữu ích sau để truy xuất thông tin:
  1. getNodeName– lấy tên máy chủ.
  2. getNodeValue– lấy giá trị nút.
  3. getNodeType– lấy loại nút.
  4. getParentNode– lấy nút chứa nút đã cho.
  5. getChildNodes– lấy tất cả các nút dẫn xuất (các nút nằm trong một nút nhất định).
  6. getAttributes– lấy tất cả các thuộc tính của nút.
  7. getOwnerDocument– lấy tài liệu của nút này.
  8. getFirstChild/getLastChild– lấy nút dẫn xuất đầu tiên/cuối cùng.
  9. getLocalName– hữu ích khi xử lý các không gian tên để lấy tên không có tiền tố.
  10. getTextContent– trả về tất cả văn bản trong một phần tử và tất cả các phần tử trong một phần tử nhất định, bao gồm cả dấu ngắt dòng và dấu cách.
Lưu ý về phương pháp 9: nó sẽ luôn trả về null trừ khi bạn đã sử dụng phương thức setNamespaceAware(true) trong DocumentFactory để kích hoạt quá trình xử lý vùng tên. Bây giờ, một chi tiết quan trọng: các phương thức này chung cho tất cả các Nút, nhưng trong Nút, chúng ta có thể có cả phần tử và thuộc tính. Và đây là những câu hỏi: một phần tử có thể có giá trị gì? Một thuộc tính có thể có những nút dẫn xuất nào? Và những người khác không nhất quán. Và mọi thứ khá đơn giản: mỗi phương thức sẽ hoạt động tùy thuộc vào loại Node . Tất nhiên, chỉ cần sử dụng logic là đủ để không bị nhầm lẫn. Ví dụ: thuộc tính có khả năng có những thuộc tính gì? Yếu tố này có ý nghĩa gì khác? Tuy nhiên, để không tự mình thử mọi thứ, trong tài liệu chính thức có một bảng rất hữu ích về cách hoạt động của từng phương pháp tùy thuộc vào loại Nút: Chất lượng hóa ra rất tệ, vì vậy đây là liên kết đến tài liệu (bảng tại đầu trang): Tài liệu về nút Điều quan trọng nhất cần nhớ:
  1. CHỈ các phần tử có thuộc tính.
  2. Các yếu tố KHÔNG có ý nghĩa.
  3. Tên của nút thành phần giống với tên của thẻ và tên của nút thuộc tính giống với tên của thuộc tính.
<h2>DOM (Mô hình đối tượng tài liệu) - THỰC HÀNH</h2>Trong phần thực hành, chúng ta sẽ phân tích các loại nhiệm vụ khác nhau khi tìm kiếm thông tin trong XML. Chúng tôi cũng thực hiện hai nhiệm vụ từ bài viết trước để so sánh sự thuận tiện. Hãy bắt đầu và sẽ rất tốt nếu bắt đầu bằng việc nhập:
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;
Tôi cung cấp thông tin nhập để bạn không nhầm lẫn giữa các lớp :) Nhiệm vụ số 1 - chúng tôi cần lấy thông tin về tất cả nhân viên và xuất thông tin đó ra bảng điều khiển từ tệp XML sau:
<?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>
Như chúng ta có thể thấy, chúng ta có tất cả thông tin được lưu trữ trong các phần tử nhân viên. Để lưu trữ nó ở đâu đó trong chương trình của chúng ta, hãy tạo một lớp 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;
    }
}
Bây giờ chúng ta đã mô tả cấu trúc lưu trữ dữ liệu, chúng ta cần một bộ sưu tập để lưu trữ nhân viên. Chúng tôi sẽ tạo nó trong chính mã đó. Chúng tôi cũng cần tạo Tài liệu dựa trên XML của mình:
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"));
    }
}
Sau khi nhận được tài liệu, chúng tôi có quyền lực vô hạn đối với toàn bộ cấu trúc của tệp XML. Chúng tôi có thể tìm nạp bất kỳ phần tử nào vào bất kỳ lúc nào, quay lại để kiểm tra bất kỳ dữ liệu nào và nói chung là một cách tiếp cận linh hoạt hơn những gì chúng tôi có trong SAX. Trong bối cảnh của nhiệm vụ này, chúng ta chỉ cần trích xuất tất cả các phần tử nhân viên, sau đó trích xuất tất cả thông tin về họ. Nó khá đơn giản:
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()));
    }
}
Mô tả về giải pháp này là đúng trong giải pháp. Đó là khuyến khích, sau khi xem mã, quay lại lý thuyết và đọc lại. Trên thực tế, mọi thứ đều rõ ràng theo bản năng. Đọc nhận xét cẩn thận và không nên có bất kỳ câu hỏi nào, và nếu có, bạn có thể viết trong nhận xét, tôi sẽ trả lời hoặc trong liên kết, hoặc chỉ cần chạy IDEA của bạn và thử tự mình chơi với mã nếu bạn vẫn chưa làm được điều đó Vì vậy, sau khi chạy mã, chúng tôi nhận được kết quả sau:
Информации о сотруднике: 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.
Như bạn có thể thấy, nhiệm vụ đã hoàn thành thành công! Hãy chuyển sang nhiệm vụ tiếp theo :) Nhiệm vụ số 2 - tên của một phần tử được nhập từ bảng điều khiển, về phần tử này bạn cần hiển thị thông tin về tất cả các phần tử bên trong nó và các thuộc tính của chúng từ tệp XML sau:
<?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>
Mọi thứ khá đơn giản: chúng ta phải lấy phần tử theo tên của nó mà chúng ta đếm, sau đó đi qua tất cả các nút con. Để làm điều này, bạn cần lặp qua tất cả các nút con của tất cả các nút con là phần tử. Giải pháp cho vấn đề này:
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());
        }
    }
}
Toàn bộ mô tả về giải pháp có trong phần nhận xét, nhưng tôi muốn minh họa một chút bằng đồ họa về cách tiếp cận mà chúng tôi đã sử dụng, sử dụng một ví dụ từ hình ảnh lý thuyết. Chúng tôi sẽ cho rằng chúng tôi cần hiển thị thông tin về thẻ html. Như bạn thấy, chúng ta cần đi từ trên xuống dưới từ gốc cây. Tất cả các dòng là các nút. Trong giải pháp, chúng ta sẽ đệ quy đi từ đầu phần tử mong muốn qua tất cả các nút của nó và nếu một trong các nút của nó là một phần tử thì chúng ta cũng lặp qua tất cả các nút của phần tử này. Vì vậy, sau khi chạy mã, chúng tôi nhận được kết quả đầu ra sau cho phần tử gốc:
Элемент был найден!
Найден элемент: 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
Vấn đề đã được giải quyết thành công! Nhiệm vụ số 3 – từ tệp XML sau, nơi lưu thông tin về sinh viên, giáo sư và nhân viên, bạn cần đọc thông tin và xuất ra bảng điều khiển:
<?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>
Nhiệm vụ khá đơn giản nhưng thú vị. Đầu tiên, chúng ta cần tạo 4 lớp: nhân viên, giáo sư và sinh viên, cũng như một lớp trừu tượng chung là Human để đưa biến tên của mỗi lớp dưới một mẫu số chung: Lớp cha trừu tượng
public abstract class Human {
    private String name;

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

    public String getName() {
        return name;
    }
}
Học sinh
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;
    }
}
Giáo sư
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;
    }
}
Người lao động
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;
    }
}
Bây giờ các lớp của chúng ta đã sẵn sàng, chúng ta chỉ cần viết mã để lấy tất cả các thành phần sinh viên, giáo sư và thành viên, sau đó lấy thuộc tính của họ. Để lưu trữ, chúng tôi sẽ sử dụng một bộ sưu tập sẽ lưu trữ các đối tượng của lớp cha chung cho tất cả - Human. Và vì vậy, giải pháp cho vấn đề này:
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;
            }
        }
    }
}
Lưu ý rằng chúng ta chỉ cần tên thành phần để lấy tất cả các thành phần này từ tài liệu. Điều này giúp đơn giản hóa rất nhiều quá trình tìm kiếm thông tin bạn cần. Tất cả thông tin về mã đều có trong phần bình luận. Không có gì mới được sử dụng mà không có trong các nhiệm vụ trước đó. Đầu ra mã:
Профессор 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
Vấn đề đã được giải quyết! Khuyến nghị khi nào nên sử dụng DOM và khi nào nên sử dụng SAX. Sự khác biệt giữa các công cụ này là ở chức năng và tốc độ. Nếu bạn cần chức năng linh hoạt hơn và có đủ khả năng để lãng phí hiệu suất chương trình thì lựa chọn của bạn là DOM, nhưng nếu mục tiêu chính của bạn là giảm chi phí bộ nhớ thì DOM không phải là lựa chọn tốt nhất, vì nó đọc tất cả thông tin từ tệp XML và lưu trữ nó. Do đó, phương pháp đọc tuần tự SAX ít tốn kém hơn. Tóm lại: nếu bạn cần hiệu suất - SAX, chức năng - DOM. <h2>Kết luận</h2>Mỗi lập trình viên đều có những công cụ riêng và tùy theo nhiệm vụ mà bạn cần sử dụng những công cụ nhất định. Trong các bài viết về SAX và DOM, mục tiêu của tôi là hướng dẫn bạn cách trích xuất thông tin từ các tệp XML và xử lý chúng theo cách bạn cần. Tuy nhiên, ngay cả khi bạn đã đọc những bài viết này, bạn cũng không thể khẳng định mình đã học cách sử dụng những công cụ này. Bạn nên thực hành, kiểm tra mã từ các bài viết, hiểu cách thức hoạt động và cố gắng tự viết một cái gì đó. Rốt cuộc, điều quan trọng nhất là thực hành. Bài báo cuối cùng sẽ được xuất bản trong những ngày tới và có vẻ như là sau khi cuộc thi kết thúc và sẽ được dành cho JAXB. JAXB là một công cụ để lưu các đối tượng trong chương trình của bạn ở định dạng XML. Chỉ vậy thôi, tôi hy vọng bài viết này hữu ích và chúc bạn lập trình may mắn :) Bài trước: [Cuộc thi] Kiến thức cơ bản về XML dành cho lập trình viên Java - Phần 3.1/3 - SAX
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION