引言 各位读者大家好,我还不是最后一篇文章,我要向你们表示祝贺:有关 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