使用 SAX 解析器,如何解析具有相同名称标签但元素不同的 xml 文件?

Posted

技术标签:

【中文标题】使用 SAX 解析器,如何解析具有相同名称标签但元素不同的 xml 文件?【英文标题】:using SAX parser, how do you parse an xml file which has same name tags but in different elements? 【发布时间】:2011-11-04 19:32:12 【问题描述】:

是否可以在 SAX 解析器中给出路径表达式?我有一个 XML 文件,它有几个相同的名称标签,但它们在不同的元素中。有什么方法可以区分它们。 这是 XML:

<Schools>
    <School>
        <ID>335823</ID> 
        <Name>Fairfax High School</Name> 
        <Student>
            <ID>4195653</ID>
            <Name>Will Turner</Name>
        </Student>
        <Student>
            <ID>4195654</ID>
            <Name>Bruce Paltrow</Name>
        </Student>
        <Student>
            <ID>4195655</ID>
            <Name>Santosh Gowswami</Name>
        </Student>
    </School>
    <School>
        <ID>335824</ID> 
        <Name>FallsChurch High School</Name> 
        <Student>
            <ID>4153</ID>
            <Name>John Singer</Name>
        </Student>
        <Student>
            <ID>4154</ID>
            <Name>Shane Warne</Name>
        </Student>
        <Student>
            <ID>4155</ID>
            <Name>Eddie Diaz</Name>
        </Student>
    </School>
</Schools>

我想将学生的姓名和 ID 与学校的名称和 ID 区分开来。

感谢您的回复:

我创建了一个学生 pojo,它具有以下字段 - school_id、school_name、student_id 和 student_name 以及它们的 getter 和 setter 方法。这是我的临时解析器实现。当我解析 xml 时,我需要将学校名称、id、学生姓名、id 的值放入 pojo 中并返回。你能告诉我我应该如何实现差异化堆栈。这是我的解析器框架::

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

public class HandleXML extends DefaultHandler 

    private student info;
    private boolean school_id = false;
    private boolean school_name = false;
    private boolean student_id = false;
    private boolean student_name = false;
    private boolean student = false;
    private boolean school = false;


    public HandleXML(student record) 
        super();
        this.info = record;
        school_id = false;
        school_name = false;
        student_id = false;
        student_name = false;
        student = false;
        school = false;
    

    @Override
    public void startElement(String uri, String localName,
            String qName, Attributes attributes)
            throws SAXException 
    if (qName.equalsIgnoreCase("student")) 
            student = true;
        
    if (qName.equalsIgnoreCase("school")) 
            school_id = true;
        
    if (qName.equalsIgnoreCase("school_id")) 
            school_id = true;
        
    if (qName.equalsIgnoreCase("student_id")) 
            student_id = true;
        
    if (qName.equalsIgnoreCase("school_name")) 
            school_name = true;
        
    if (qName.equalsIgnoreCase("student_name")) 
            student_name = true;
        
    

    @Override
    public void endElement(String uri, String localName,
            String qName)
            throws SAXException 
    

    @Override
    public void characters(char ch[], int start, int length)
            throws SAXException 

        String data = new String(ch, start, length);

    

【问题讨论】:

另请查看:***.com/questions/1863250 - 有些项目允许您将 XPath 的子集与流式文档一起使用。如果您可以将您的问题放入该子集中,则生成的代码将比任何手动 SAX 处理程序代码更可取。 带上下文的 SAX 解析就像一个状态机:en.wikipedia.org/wiki/Finite-state_machine,你需要有一些可以打开/关闭的标志来知道你在哪里,但它很快就会变得非常混乱,你在你走得太远之前应该考虑替代方案。 【参考方案1】:

在 SAX 解析器中,您将按文档顺序获得每个元素。您必须维护一个堆栈来跟踪嵌套(在处理 startElement 时压入堆栈,并为 endElement 弹出)。您可以通过当前堆栈中的内容来区分不同的 &lt;Name&gt; 元素。

或者,只保留一个变量来告诉您是否遇到了&lt;School&gt; 标记或&lt;Student&gt; 标记来告诉您您看到的是哪种类型的&lt;Name&gt;

【讨论】:

+1 用于保持堆栈,这就是要走的路。您可以通过打印堆栈的当前内容来生成类似 Xpath 的字符串。使用标志来判断你所在的标签是丑陋的。 :-P【参考方案2】:

嗯,我已经好几年没有在 Java 中使用 SAX 了,所以这是我的看法:

package play.xml.sax;

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;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

public class Test1 
    public static void main(String[] args) 
        SAXParserFactory spf = SAXParserFactory.newInstance();
        SchoolsHandler handler = new SchoolsHandler();
        try 
            SAXParser sp = spf.newSAXParser();
            sp.parse("schools.xml", handler);
            System.out.println("Number of read schools: " + handler.getSchools().size());
         catch (SAXException se) 
            se.printStackTrace();
         catch (ParserConfigurationException pce) 
            pce.printStackTrace();
         catch (IOException ie) 
            ie.printStackTrace();
        
    


class SchoolsHandler extends DefaultHandler 
    private static final String TAG_SCHOOLS = "Schools";
    private static final String TAG_SCHOOL = "School";
    private static final String TAG_STUDENT = "Student";
    private static final String TAG_ID = "ID";
    private static final String TAG_NAME = "Name";

    private final Stack<String> tagsStack = new Stack<String>();
    private final StringBuilder tempVal = new StringBuilder();

    private List<School> schools;
    private School school;
    private Student student;

    public void startElement(String uri, String localName, String qName, Attributes attributes) 
        pushTag(qName);
        tempVal.setLength(0);
        if (TAG_SCHOOLS.equalsIgnoreCase(qName)) 
            schools = new ArrayList<School>();
         else if (TAG_SCHOOL.equalsIgnoreCase(qName)) 
            school = new School();
         else if (TAG_STUDENT.equalsIgnoreCase(qName)) 
            student = new Student();
        
    

    public void characters(char ch[], int start, int length) 
        tempVal.append(ch, start, length);
    

    public void endElement(String uri, String localName, String qName) 
        String tag = peekTag();
        if (!qName.equals(tag)) 
            throw new InternalError();
        

        popTag();
        String parentTag = peekTag();

        if (TAG_ID.equalsIgnoreCase(tag)) 
            int id = Integer.valueOf(tempVal.toString().trim());
            if (TAG_STUDENT.equalsIgnoreCase(parentTag)) 
                student.setId(id);
             else if (TAG_SCHOOL.equalsIgnoreCase(parentTag)) 
                school.setId(id);
            
         else if (TAG_NAME.equalsIgnoreCase(tag)) 
            String name = tempVal.toString().trim();
            if (TAG_STUDENT.equalsIgnoreCase(parentTag)) 
                student.setName(name);
             else if (TAG_SCHOOL.equalsIgnoreCase(parentTag)) 
                school.setName(name);
            
         else if (TAG_STUDENT.equalsIgnoreCase(tag)) 
            school.addStudent(student);
         else if (TAG_SCHOOL.equalsIgnoreCase(tag)) 
            schools.add(school);
        
    

    public void startDocument() 
        pushTag("");
    

    public List<School> getSchools() 
        return schools;
    

    private void pushTag(String tag) 
        tagsStack.push(tag);
    

    private String popTag() 
        return tagsStack.pop();
    

    private String peekTag() 
        return tagsStack.peek();
    


class School 
    private int id;
    private String name;
    private List<Student> students = new ArrayList<Student>();

    public String getName() 
        return name;
    

    public void setName(String name) 
        this.name = name;
    

    public int getId() 
        return id;
    

    public void setId(int id) 
        this.id = id;
    

    public void addStudent(Student student) 
        students.add(student);
    

    public List<Student> getStudents() 
        return students;
    


class Student 
    private int id;
    private String name;

    public String getName() 
        return name;
    

    public void setName(String name) 
        this.name = name;
    

    public int getId() 
        return id;
    

    public void setId(int id) 
        this.id = id;
    

schools.xml 包含您的示例 XML。请注意,我将所有内容都塞进了一个文件中,但这只是因为我只是在玩耍。

【讨论】:

【参考方案3】:

是的,使用 SAX 解析器理解 xml 通常比使用 DOM 复杂一些。基本上,您需要在 SAX 解析器中维护状态/上下文,以便区分这些情况。

注意,实现 SAX 处理程序的另一个关键是了解值可能会在多个字符事件中拆分。

【讨论】:

【参考方案4】:

Sax 是基于事件的,通过回调您可以连续读取 XML 文档。 Sax 非常适合读取大型 XML 文档,因为整个文档不会加载到内存中。你可能想看看Xpath,例如

XPathFactory xPathFactory = XPathFactory.newInstance();
XPath xPath = xPathFactory.newXPath();
String expression = "/Schools/school/ ...";
XPathExpression xPathExpression = xPath.compile(expression);
// Compile the expression to get a XPathExpression object.
Object result = xPathExpression.evaluate(xmlDocument);

【讨论】:

这在 SAX 中是可能的,正如 Jim Garrison 的回答中所解释的那样。 @Don Roby,可以进行更正,但您真的会用 SAX 这样做吗?我认为以这种方式使用 SAX 过于复杂,可以使用 XPath 以更简洁的方式实现(没有标志,没有堆栈),但这只是我的看法 编写 SAX 应用程序需要更多的工作,但如果您需要 SAX 提供的内存节省并且能够承担额外的编程工作来实现它,那么它是完全合法的。但是,我认为您的担心是对的:需要提出此线程中提出的问题的人在使用 SAX 时会遇到很多困难。 @Michael Kay 唷有人同意 :) 其他人否决了我 :( 好吧,您的开场白是“萨克斯不可能做到这一点”。无论您是否认为这是最好的解决方案,这肯定是可能的【参考方案5】:
private boolean isInStudentNode;
...................................................    

public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException 
    // enter node Student
    if(qName.equalEgnoreCase("Student")
       isInStudentNode = true;
    
    ...


public void endElement(String uri, String localName, String qName) throws SAXException 
    // end node Student
    if(qName.equalEgnoreCase("Student")
       isInStudentNode = false;
       ...........
    

    // end node Name (school|student)
    if(qName.equalEgnoreCase("Name")
        if(isInStudentNode) student.setName(...);
        else school.setName(...);
    

它和我一起工作

【讨论】:

以上是关于使用 SAX 解析器,如何解析具有相同名称标签但元素不同的 xml 文件?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 SAX 解析器解析名称空间?

xml 使用 sax 解析器从子标签解析数据

如果输入文件中未指定 DTD,如何强制 SAX 解析器使用 DTD?

使用 SAX 解析器解析自关闭 XML 标记时遇到问题

71 SAX解析XML流程

Android SAX 解析器没有从标签之间获取全文