引言 各位讀者大家好,我還不是最後一篇文章,我要向你們表示祝賀:有關 XML 的複雜內容已經成為過去。本文將包含 Java 程式碼。會有一點理論,然後實踐。 由於 SAX 上的一篇資料在 Word 中佔了 10 頁,我意識到我無法適應限制。因此,第 3 條將分為 3 條單獨的文章,無論聽起來多麼奇怪。一切都將按以下順序進行:SAX -> DOM -> JAXB。本文將僅關注 SAX。PS 課程中某個地方有一個任務,需要在 HTML 檔案中顯示所有內部元素。讀完本文後,您將無需逐行閱讀常規
BufferedReader
且複雜的處理演算法即可做到這一點,並且在最後一個實際範例中也會給出類似的解決方案。讓我們開始吧:) SAX(XML 的簡單 API)- 理論 SAX 處理程序的設計方式是,它只是按順序讀取XML 檔案並對不同的事件做出反應,然後將資訊傳遞給特殊的事件處理程序。 它有很多事件,但最常見和最有用的事件如下:
startDocument
— 文檔的開頭endDocument
- 文檔結尾startElement
- 打開一個元素endElement
- 關閉一個元素characters
— 元素內的文字訊息。
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
和方法endElement
:uri
- 這是元素所在的空間,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
。要讀取元素內的文本,我們有字元方法。為此,我們需要建立一個具有改進邏輯的新處理程序類別。不要忘記處理程序是成熟的類,能夠儲存任何複雜的邏輯。因此,現在我們將調整我們的處理器。事實上,只要注意我們總是輪流就足夠了name
,job
無論以什麼順序,我們都可以輕鬆地將姓名和職業保存到單獨的變數中,當兩個變數都保存時,創建我們的員工。只是在這裡,以及元素的開頭,我們沒有元素內文字的參數。我們需要對文字使用方法。但是,如果這些是完全不同的方法,我們要如何取得元素內的文字資訊呢?我的解決方案:我們只需要記住最後一個元素的名稱,並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>
正如您所看到的,我們這裡有三種可能的情況:root
、mysql
、oracle
。然後程式會顯示裡面所有元素的所有資訊。我們該怎麼做?這很簡單:我們只需要宣告一個邏輯變量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;
}
}
}
在此程式碼中,當輸入需要有關資訊的元素時,我們將標誌設為isEntered
true,這表示我們位於該元素內部。一旦我們進入元素內部,我們只需處理每個新元素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
GO TO FULL VERSION