JavaRush /Java 博客 /Random-ZH /Java 程序员的 XML 基础知识 - 第 3.1 部分(共 3 部分)- SAX
Ярослав
第 40 级
Днепр

Java 程序员的 XML 基础知识 - 第 3.1 部分(共 3 部分)- SAX

已在 Random-ZH 群组中发布
引言 各位读者大家好,我还不是最后一篇文章,我要向你们表示祝贺:有关 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;
        }
    }
}
在此代码中,当输入需要有关信息的元素时,我们将标志设置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
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION