【问题标题】:using SAX parser, how do you parse an xml file which has same name tags but in different elements?使用 SAX 解析器,如何解析具有相同名称标签但元素不同的 xml 文件?
【发布时间】: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);

    }
}

【问题讨论】:

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

标签: java xml xpath xml-parsing saxparser


【解决方案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(...);
          }
      }
      

      它和我一起工作

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-06
        • 1970-01-01
        • 2015-09-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多