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.1/3 - SAX

Xuất bản trong nhóm
Giới thiệu Xin chào tất cả các độc giả của bài viết chưa phải cuối cùng của tôi và tôi muốn chúc mừng bạn: những vấn đề phức tạp về XML đã ở phía sau chúng ta. Bài viết này sẽ chứa mã bằng Java. Sẽ có một chút lý thuyết, sau đó thực hành. Do thực tế là một phần tài liệu trên SAX đã lấp đầy 10 trang trong Word, tôi nhận ra rằng mình không thể đáp ứng được giới hạn. Vì vậy, bài 3 sẽ được chia thành 3 bài riêng biệt, dù nghe có vẻ lạ lùng đến đâu. Mọi thứ sẽ theo thứ tự sau: SAX -> DOM -> JAXB. Bài viết này sẽ chỉ tập trung vào SAX. Tái bút Có một nhiệm vụ ở đâu đó trong khóa học cần hiển thị tất cả các thành phần bên trong trong một tệp HTML. Sau bài viết này, bạn sẽ có thể thực hiện việc này mà không cần đọc từng dòng BufferedReadercác thuật toán xử lý thông thường và phức tạp, đồng thời giải pháp tương tự sẽ được đưa ra trong ví dụ thực tế cuối cùng. Hãy bắt đầu :) SAX (API đơn giản cho XML) - LÝ THUYẾT Trình xử lý SAX được thiết kế theo cách nó chỉ đọc các tệp XML một cách tuần tự và phản ứng với các sự kiện khác nhau, sau đó nó chuyển thông tin đến một trình xử lý sự kiện đặc biệt. Nó có khá nhiều sự kiện, nhưng những sự kiện thường xuyên và hữu ích nhất là:
  1. startDocument- phần đầu của tài liệu
  2. endDocument- Kết thúc tài liệu
  3. startElement- mở một phần tử
  4. endElement- đóng một phần tử
  5. characters— thông tin văn bản bên trong các phần tử.
Tất cả các sự kiện đều được xử lý trong một trình xử lý sự kiện , nó phải được tạo và ghi đè các phương thức . Ưu điểm: hiệu suất cao nhờ phương pháp đọc dữ liệu “trực tiếp”, chi phí bộ nhớ thấp. Nhược điểm: chức năng hạn chế, nghĩa là trong các bài toán phi tuyến tính chúng ta sẽ phải tinh chỉnh nó. SAX (API đơn giản cho XML) – THỰC HÀNH Ngay lập tức liệt kê danh sách nhập để bạn không tìm kiếm và nhầm lẫn bất cứ điều gì:
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
Bây giờ, trước tiên, chúng ta cần tạo SAXParser:
public class SAXExample {
    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        // Creation фабрики и образца parserа
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser parser = factory.newSAXParser();
    }
}
Như bạn có thể thấy, trước tiên bạn cần tạo một nhà máy, sau đó tạo chính trình phân tích cú pháp trong nhà máy đó. Bây giờ chúng ta đã có trình phân tích cú pháp, chúng ta cần một trình xử lý cho các sự kiện của nó. Đối với điều này, chúng ta cần một lớp riêng biệt để thuận tiện cho chúng ta:
public class SAXExample {
    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser parser = factory.newSAXParser();
    }

    private static class XMLHandler extends DefaultHandler {
        @Override
        public void startDocument() throws SAXException {
            // Тут будет логика реакции на начало documentа
        }

        @Override
        public void endDocument() throws SAXException {
            // Тут будет логика реакции на конец documentа
        }

        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            // Тут будет логика реакции на начало element
        }

        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException {
            // Тут будет логика реакции на конец element
        }

        @Override
        public void characters(char[] ch, int start, int length) throws SAXException {
            // Тут будет логика реакции на текст между elementми
        }

        @Override
        public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
            // Тут будет логика реакции на пустое пространство внутри элементов (пробелы, переносы строчек и так далее).
        }
    }
}
Chúng tôi đã tạo một lớp với tất cả các phương thức cần thiết để xử lý các sự kiện được liệt kê trong lý thuyết. Thêm một chút lý thuyết bổ sung: Một chút về characters: nếu phần tử chứa văn bản, ví dụ: “ xin chào ”, thì về mặt lý thuyết, phương thức này có thể được gọi 5 lần liên tiếp cho mỗi ký tự riêng lẻ, nhưng điều này không đáng sợ, vì mọi thứ đều vẫn sẽ hoạt động. Về các phương thức startElementendElement:uri - đây là khoảng trống chứa phần tử, localName- đây là tên của phần tử không có tiền tố, qName- đây là tên của phần tử có tiền tố (nếu có, nếu không thì chỉ là tên của phần tử). urilocalNameluôn trống nếu chúng tôi chưa kích hoạt xử lý không gian trong nhà máy. Điều này được thực hiện bằng cách sử dụng phương pháp nhà máy setNamespaceAware(true). Sau đó, chúng ta có thể nhận được khoảng trắng ( uri) và các phần tử có tiền tố đứng trước chúng ( localName). Nhiệm vụ số 1 - Chúng tôi có 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>
Mục tiêu của chúng tôi: lấy tất cả thông tin về tất cả nhân viên từ tệp này. Đầu tiên chúng ta cần tạo một lớpEmployee:
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;
    }
}
Và trong lớp chính của chúng ta, SAXExamplechúng ta cần một danh sách gồm tất cả nhân viên:
private static ArrayList<Employee> employees = new ArrayList<>();
Bây giờ chúng ta hãy xem xét kỹ thông tin chúng ta cần nằm ở đâu trong tệp XML. Và như chúng ta có thể thấy, tất cả thông tin chúng ta cần là thuộc tính của các phần tử employee. Và vì startElementchúng ta có một tham số hữu ích như attributes, nên chúng ta có một nhiệm vụ khá đơn giản. Đầu tiên, hãy loại bỏ các phương thức không cần thiết để không làm lộn xộn mã của chúng ta. Chúng tôi chỉ cần startElement. Và trong chính phương thức này, chúng ta phải thu thập thông tin từ các thuộc tính của thẻ nhân viên. Chú ý:
public class SAXExample {
    private static ArrayList<Employee> employees = new ArrayList<>();

    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser parser = factory.newSAXParser();
    }

    private static class XMLHandler extends DefaultHandler {
        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            if (qName.equals("employee")) {
                String name = attributes.getValue("name");
                String job = attributes.getValue("job");
                employees.add(new Employee(name, job));
            }
        }
    }
}
Logic rất đơn giản: nếu tên của một phần tử là employee, chúng ta sẽ chỉ nhận được thông tin về các thuộc tính của nó. Có attributesmột phương pháp hữu ích khi biết tên của một thuộc tính, bạn có thể nhận được giá trị của nó. Đó là những gì chúng tôi đã sử dụng. Bây giờ chúng ta đã tạo một trình xử lý sự kiện cho phần đầu của một phần tử, chúng ta cần phân tích cú pháp tệp XML của mình . Để làm điều này, chỉ cần làm điều này:
public class SAXExample {
    private static ArrayList<Employee> employees = new ArrayList<>();

    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser parser = factory.newSAXParser();

        XMLHandler handler = new XMLHandler();
        parser.parse(new File("resource/xml_file1.xml"), handler);

        for (Employee employee : employees)
            System.out.println(String.format("Name сотрудника: %s, его должность: %s", employee.getName(), employee.getJob()));
    }

    private static class XMLHandler extends DefaultHandler {
        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            if (qName.equals("employee")) {
                String name = attributes.getValue("name");
                String job = attributes.getValue("job");
                employees.add(new Employee(name, job));
            }
        }
    }
}
Trong phương thức phân tích cú pháp, bạn phải chuyển đường dẫn đến tệp xml và trình xử lý bạn đã tạo. Và do đó, bằng cách sử dụng mã này, chúng tôi đã trích xuất thông tin từ XML này:
<?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>
Và 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
Nhiệm vụ đã hoàn thành! Nhiệm vụ số 2 - chúng tôi có XML sau:
<?xml version="1.0" encoding="UTF-8"?>
<company>
    <name>IT-Heaven</name>
    <offices>
        <office floor="1" room="1">
            <employees>
                <employee>
                    <name>Maksim</name>
                    <job>Middle Software Developer</job>
                </employee>
                <employee>
                    <name>Ivan</name>
                    <job>Junior Software Developer</job>
                </employee>
                <employee>
                    <name>Franklin</name>
                    <job>Junior Software Developer</job>
                </employee>
            </employees>
        </office>
        <office floor="1" room="2">
            <employees>
                <employee>
                    <name>Herald</name>
                    <job>Middle Software Developer</job>
                </employee>
                <employee>
                    <name>Adam</name>
                    <job>Middle Software Developer</job>
                </employee>
                <employee>
                    <name>Leroy</name>
                    <job>Junior Software Developer</job>
                </employee>
            </employees>
        </office>
    </offices>
</company>
Mục tiêu của chúng tôi: lấy tất cả thông tin về tất cả nhân viên từ tệp này. Vấn đề này sẽ chứng minh rõ ràng rằng một tệp XML có cấu trúc kém có thể khiến việc viết mã trở nên khó khăn hơn như thế nào. Như bạn có thể thấy, thông tin về tên và vị trí hiện được lưu trữ dưới dạng thông tin văn bản bên trong các phần tử nameand job. Để đọc văn bản bên trong các phần tử, chúng ta có phương thức character. Để làm điều này, chúng ta cần tạo một lớp xử lý mới với logic được cải tiến. Đừng quên rằng các trình xử lý là các lớp chính thức có khả năng lưu trữ logic ở bất kỳ mức độ phức tạp nào. Vì vậy, bây giờ chúng tôi sẽ điều chỉnh bộ xử lý của mình. Trên thực tế, chỉ cần lưu ý rằng chúng tôi luôn namethay jobphiên nhau và không quan trọng theo thứ tự nào, chúng tôi có thể dễ dàng lưu tên và nghề nghiệp vào các biến riêng biệt và khi cả hai biến được lưu, hãy tạo nhân viên của chúng tôi. Chỉ ở đây, cùng với phần đầu của phần tử, chúng ta không có tham số cho văn bản bên trong phần tử. Chúng ta cần sử dụng các phương thức trên văn bản. Nhưng làm cách nào để lấy thông tin văn bản bên trong một phần tử nếu đây là các phương thức hoàn toàn khác nhau? Giải pháp của tôi: chúng ta chỉ cần nhớ tên của phần tử cuối cùng và characterskiểm tra xem chúng ta đang đọc thông tin ở phần tử nào. Bạn cũng cần nhớ rằng các ký tự <codee> đọc tất cả các ký tự bên trong các phần tử, điều đó có nghĩa là tất cả các khoảng trắng và thậm chí cả dấu ngắt dòng sẽ được đọc. Và chúng tôi không cần chúng. Chúng ta cần bỏ qua dữ liệu này vì nó không chính xác.</codee> Mã:
public class SAXExample {
    private static ArrayList<Employee> employees = new ArrayList<>();

    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser parser = factory.newSAXParser();

        AdvancedXMLHandler handler = new AdvancedXMLHandler();
        parser.parse(new File("resource/xml_file2.xml"), handler);

        for (Employee employee : employees)
            System.out.println(String.format("Name сотрудника: %s, его должность: %s", employee.getName(), employee.getJob()));
    }

    private static class AdvancedXMLHandler extends DefaultHandler {
        private String name, job, lastElementName;

        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            lastElementName = qName;
        }

        @Override
        public void characters(char[] ch, int start, int length) throws SAXException {
            String information = new String(ch, start, length);

            information = information.replace("\n", "").trim();

            if (!information.isEmpty()) {
                if (lastElementName.equals("name"))
                    name = information;
                if (lastElementName.equals("job"))
                    job = information;
            }
        }

        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException {
            if ( (name != null && !name.isEmpty()) && (job != null && !job.isEmpty()) ) {
                employees.add(new Employee(name, job));
                name = null;
                job = null;
            }
        }
    }
}
Như bạn có thể thấy, do sự phức tạp tầm thường của cấu trúc tệp XML, mã của chúng tôi đã trở nên phức tạp hơn đáng kể. Tuy nhiên, mã này không phức tạp. Mô tả: chúng tôi đã tạo các biến để lưu trữ dữ liệu về nhân viên ( name, job) , cũng như một biến lastElementNameđể ghi lại phần tử nào chúng tôi có trong đó. Sau đó, trong phương thức characterschúng tôi lọc thông tin và nếu vẫn còn thông tin ở đó thì điều này có nghĩa rằng đây là văn bản chúng tôi cần và sau đó chúng tôi xác định xem đó là tên hay nghề nghiệp bằng cách sử dụng lastElementName. Trong phương thức endElement, chúng tôi kiểm tra xem tất cả thông tin đã được đọc chưa và nếu có, chúng tôi sẽ tạo một nhân viên và đặt lại thông tin. Đầu ra của giải pháp tương đương với ví dụ đầu tiên:
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ư vậy, vấn đề này đã được giải quyết , nhưng bạn có thể nhận thấy độ phức tạp cao hơn. Do đó, chúng ta có thể kết luận rằng việc lưu trữ thông tin văn bản trong các thuộc tính thường sẽ chính xác hơn so với việc lưu trữ thông tin văn bản trong các phần tử riêng lẻ. Và một nhiệm vụ thú vị nữa sẽ giải quyết được phần nào vấn đề trong JavaRush về việc hiển thị thông tin về một phần tử trong HTML, chỉ có điều nó sẽ cần chỉnh sửa một chút, ở đây chúng ta sẽ chỉ liệt kê tất cả các phần tử bên trong một phần tử :) Nhiệm vụ số 3 - cho phần tử phần tử, hiển thị tên và thuộc tính của tất cả các phần tử bên trong; nếu không tìm thấy phần tử, hiển thị phần tử này. Đối với nhiệm vụ này, chúng tôi sẽ sử dụng 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>
Như bạn có thể thấy, chúng ta có ba tình huống có thể xảy ra ở đây: root, mysql, oracle. Khi đó chương trình sẽ hiển thị toàn bộ thông tin về tất cả các phần tử bên trong. Làm thế nào chúng ta có thể làm điều này? Và nó khá đơn giản: chúng ta chỉ cần khai báo một biến logic isEntered, biến này sẽ cho biết chúng ta có cần phần tử bên trong hay không, và nếu ở bên trong thì sẽ đọc tất cả dữ liệu từ startElement. Mã giải pháp:
public class SAXExample {
    private static boolean isFound;

    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser parser = factory.newSAXParser();

        SearchingXMLHandler handler = new SearchingXMLHandler("root");
        parser.parse(new File("resource/xml_file3.xml"), handler);

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

    private static class SearchingXMLHandler extends DefaultHandler {
        private String element;
        private boolean isEntered;

        public SearchingXMLHandler(String element) {
            this.element = element;
        }

        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            if (isEntered) {
                System.out.println(String.format("Найден элемент <%s>, его атрибуты:", qName));

                int length = attributes.getLength();
                for(int i = 0; i < length; i++)
                    System.out.println(String.format("Name атрибута: %s, его meaning: %s", attributes.getQName(i), attributes.getValue(i)));
            }

            if (qName.equals(element)) {
                isEntered = true;
                isFound = true;
            }
        }

        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException {
            if (qName.equals(element))
                isEntered = false;
        }
    }
}
Trong mã này, khi nhập một phần tử mà chúng tôi cần thông tin, chúng tôi đặt cờ isEnteredthành true, có nghĩa là chúng tôi đang ở bên trong phần tử đó. Và ngay khi chúng ta ở bên trong phần tử, chúng ta chỉ cần xử lý từng phần tử mới startElement, biết rằng đó chính xác là một phần tử bên trong phần tử của chúng ta. Vì vậy chúng ta xuất ra tên phần tử và tiêu đề của nó. Nếu không tìm thấy phần tử trong tệp thì chúng ta có một biến isFoundđược đặt khi tìm thấy phần tử đó và nếu sai, một thông báo sẽ hiển thị rằng không tìm thấy phần tử đó. Và như bạn có thể thấy, trong ví dụ này SearchingXMLHandlerchúng ta đã truyền rootmột phần tử cho hàm tạo. Kết luận dành cho anh:
Найден элемент <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ì vậy, chúng tôi đã nhận được tất cả thông tin về các phần tử bên trong và thuộc tính của chúng. Vấn đề đã được giải quyết. <h2>Phần kết</h2>Bạn đã thấy SAX là một công cụ khá thú vị và khá hiệu quả, nó có thể được sử dụng theo nhiều cách khác nhau, cho những mục đích khác nhau, v.v., bạn chỉ cần nhìn vấn đề từ bên phải bên, như trong nhiệm vụ số 2 và số 3, trong đó SAX không đưa ra các phương pháp trực tiếp để giải quyết vấn đề, nhưng nhờ sự khéo léo của mình, chúng tôi đã tìm ra cách thoát khỏi tình huống này. Phần tiếp theo của bài viết sẽ dành hoàn toàn cho DOM. Tôi hy vọng bạn thích làm quen với SAX. Hãy thử nghiệm, thực hành và bạn sẽ hiểu rằng mọi thứ khá đơn giản. Và chỉ vậy thôi, chúc các bạn lập trình may mắn và mong sớm có phần về DOM. Chúc bạn học tập 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 2/3 Bài tiếp theo: [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.2/3 - DOM
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION