JavaRush /Java Blog /Random-TW /Java 程式設計師的 XML 基礎知識 - 第 3.1 部分(共 3 部分)- SAX
Ярослав
等級 40
Днепр

Java 程式設計師的 XML 基礎知識 - 第 3.1 部分(共 3 部分)- SAX

在 Random-TW 群組發布
引言 各位讀者大家好,我還不是最後一篇文章,我要向你們表示祝賀:有關 XML 的複雜內容已經成為過去。本文將包含 Java 程式碼。會有一點理論,然後實踐。 由於 SAX 上的一篇資料在 Word 中佔了 10 頁,我意識到我無法適應限制。因此,第 3 條將分為 3 條單獨的文章,無論聽起來多麼奇怪。一切都將按以下順序進行:SAX -> DOM -> JAXB。本文將僅關注 SAX。PS 課程中某個地方有一個任務,需要在 HTML 檔案中顯示所有內部元素。讀完本文後,您將無需逐行閱讀常規BufferedReader且複雜的處理演算法即可做到這一點,並且在最後一個實際範例中也會給出類似的解決方案。讓我們開始吧:) SAX(XML 的簡單 API)- 理論 SAX 處理程序的設計方式是,它只是按順序讀取XML 檔案並對不同的事件做出反應,然後將資訊傳遞給特殊的事件處理程序。 它有很多事件,但最常見和最有用的事件如下:
  1. startDocument— 文檔的開頭
  2. endDocument- 文檔結尾
  3. startElement- 打開一個元素
  4. endElement- 關閉一個元素
  5. characters— 元素內的文字訊息。
所有事件都在事件處理程序中處理,必須建立事件處理程序並重寫方法優點:由於「直接」讀取資料的方法而具有高效能,記憶體成本低。 缺點:功能有限,這意味著在非線性問題中我們必須對其進行改進。 SAX (Simple API for XML) – 立即練習導入列表,這樣您就不會搜尋和混淆任何內容:
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;
現在,首先,我們需要建立一個 SAXParser:
public class SAXExample {
    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        // Creation фабрики и образца parserа
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser parser = factory.newSAXParser();
    }
}
正如您所看到的,您首先需要建立一個工廠,然後在工廠中建立解析器本身。現在我們有了解析器本身,我們需要一個處理它的事件的處理程序。為此,為了方便起見,我們需要一個單獨的類別:
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 {
            // Тут будет логика реакции на пустое пространство внутри элементов (пробелы, переносы строчек и так далее).
        }
    }
}
我們創建了一個類,其中包含處理理論中列出的事件所需的所有方法。更多額外的理論:一點 關於characters如果元素包含文本,例如“ hello ”,那麼理論上,該方法可以為每個單獨的字符連續調用 5 次,但這並不可怕,因為一切仍然會工作。 關於startElement和方法endElementuri - 這是元素所在的空間,localName- 這是不帶前綴的元素的名稱,qName- 這是帶有前綴的元素的名稱(如果有的話,否則只是名稱的元素)。 如果我們沒有在工廠啟用空間處理,uri則始終為空。localName這是使用工廠方法完成的setNamespaceAware(true)。然後我們可以得到空格(uri)和前面帶有前綴的元素(localName)。 任務 #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;
    }
}
在我們的主類別中,SAXExample我們需要一個包含所有員工的清單:
private static ArrayList<Employee> employees = new ArrayList<>();
現在讓我們仔細看看 XML 檔案中我們需要的資訊在哪裡。而且,正如我們所看到的,我們需要的所有資訊就是元素的屬性employee。既然startElement我們有一個如此有用的參數attributes,那麼我們就有一個相當簡單的任務。首先,讓我們刪除不必要的方法,以免使我們的程式碼變得混亂。我們只需要startElement. 在方法本身中,我們必須從員工標籤的屬性中收集資訊。注意力:
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));
            }
        }
    }
}
邏輯很簡單:如果元素的名稱是employee,我們將簡單地接收有關其屬性的資訊。有attributes一個有用的方法,知道屬性的名稱,就可以得到它的值。這就是我們用的。現在我們已經為元素的開頭建立了一個事件處理程序,我們需要解析 XML 檔案。為此,只需執行以下操作:
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));
            }
        }
    }
}
在 parse 方法中,您必須傳遞 xml 檔案的路徑和您建立的處理程序。因此,我們使用此程式碼從 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>
我們得到以下輸出:
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"?>
<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>
我們的目標:從該文件中獲取所有員工的所有資訊。這個問題將很好地展示結構不良的 XML 檔案如何使編寫程式碼變得更加困難。如您所見,有關名稱和位置的資訊現在作為文字資訊儲存在name和元素內job。要讀取元素內的文本,我們有字元方法。為此,我們需要建立一個具有改進邏輯的新處理程序類別。不要忘記處理程序是成熟的類,能夠儲存任何複雜的邏輯。因此,現在我們將調整我們的處理器。事實上,只要注意我們總是輪流就足夠了namejob無論以什麼順序,我們都可以輕鬆地將姓名和職業保存到單獨的變數中,當兩個變數都保存時,創建我們的員工。只是在這裡,以及元素的開頭,我們沒有元素內文字的參數。我們需要對文字使用方法。但是,如果這些是完全不同的方法,我們要如何取得元素內的文字資訊呢?我的解決方案:我們只需要記住最後一個元素的名稱,並characters檢查我們正在讀取哪個元素的資訊。您還需要記住 <codee>characters 讀取元素內的所有字符,這意味著所有空格甚至換行符都將被讀取。我們不需要它們。我們需要忽略這個數據,因為它是不正確的。</codee>代碼:
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;
            }
        }
    }
}
正如您所看到的,由於 XML 檔案結構的平庸複雜性,我們的程式碼變得更加複雜。不過,程式碼並不複雜。 描述:我們創建了變數來儲存有關員工( name, job)的數據,以及一個變數lastElementName來記錄我們所在的元素。之後,在方法中characters我們過濾訊息,如果仍然有訊息,那麼這意味著這是我們需要的文本,然後我們使用 來確定它是一個名字還是一個職業lastElementName。在該方法中endElement,我們檢查是否已讀取所有信息,如果是,則建立員工並重設資訊。此解決方案的輸出相當於第一個範例:
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
這樣,這個問題就解決了,但是你可以發現複雜度更高了。因此,我們可以得出結論,將文字資訊儲存在屬性中通常比儲存在單一元素中更正確。還有一個更貼心的任務,將部分解決 JavaRush 中關於在 HTML 中顯示有關元素的信息的問題,只需對其進行一些編輯,這裡我們將簡單地列出元素內的所有元素:) 任務 3 -給定element 元素,顯示所有內部元素的名稱和屬性;如果找不到該元素,則顯示此元素。 對於此任務,我們將使用以下 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>
正如您所看到的,我們這裡有三種可能的情況:rootmysqloracle。然後程式會顯示裡面所有元素的所有資訊。我們該怎麼做?這很簡單:我們只需要宣告一個邏輯變量isEntered,它會指示我們是否需要裡面的元素,如果在裡面,則會讀取 中的所有資料startElement解決方案代碼:
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;
        }
    }
}
在此程式碼中,當輸入需要有關資訊的元素時,我們將標誌設為isEnteredtrue,這表示我們位於該元素內部。一旦我們進入元素內部,我們只需處理每個新元素startElement,知道它正是我們元素的內部元素。所以我們輸出元素名稱及其標題。如果在檔案中沒有找到該元素,那麼我們會isFound在找到該元素時設定一個變量,如果該變數為 false,則會顯示一則訊息,指出未找到該元素。正如您所看到的,在範例中SearchingXMLHandler我們將root一個元素傳遞給建構函數。對他的結論是:
Найден элемент <oracle>, его атрибуты:
Найден элемент <connection>, его атрибуты:
Name атрибута: value, его meaning: jdbc:oracle:thin:@10.220.140.48:1521:test1
Найден элемент <user>, его атрибуты:
Name атрибута: value, его meaning: secretOracleUsername
Найден элемент <password>, его атрибуты:
Name атрибута: value, его meaning: 111
Найден элемент <mysql>, его атрибуты:
Найден элемент <connection>, его атрибуты:
Name атрибута: value, его meaning: jdbc:mysql:thin:@10.220.140.48:1521:test1
Найден элемент <user>, его атрибуты:
Name атрибута: value, его meaning: secretMySQLUsername
Найден элемент <password>, его атрибуты:
Name атрибута: value, его meaning: 222
因此,我們收到了有關內部元素及其屬性的所有資訊。問題已經解決了。 <h2>Epilogue</h2>你已經看到SAX是一個相當有趣的工具並且非常有效,並且它可以以不同的方式使用,用於不同的目的等等,你只需要從正確的角度看問題另一方面,如任務2 和任務3 所示,SAX 沒有提供解決問題的直接方法,但是,由於我們的聰明才智,我們能夠想出一種擺脫這種情況的方法。本文的下一部分將完全致力於 DOM。我希望您喜歡了解 SAX。實驗、實踐,你就會明白一切都很簡單。就這樣,祝您編程順利,並期待盡快看到有關 DOM 的部分。祝你學習順利 :) 上一篇文章:[競賽] Java 程式設計師的 XML 基礎知識 - 第 2 部分(共 3 部分) 下一篇文章:[競賽] Java 程式設計師的XML 基礎知識- 第3.2 部分(共3 部分) - DOM
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION