• Android 中解析 XML


    对于这三种解析器各有优点,我个人比较倾向于 PULL 解析器,因为 SAX 解析器操作起来太笨重,DOM 不适合文档较大,内存较小的场景,唯有 PULL 轻巧灵活,速度快,占用内存小,使用非常顺手。读者也可以根据自己的喜好选择相应的解析技术。
    
    XML 在各种开发中都广泛应用,Android 也不例外。作为承载数据的一个重要角色,如何读写 XML 成为 Android 开发中一项重要的技能。今天就由我向大家介绍一下在 Android平台下几种常见的 XML 解析和创建的方法。
    
    在 Android 中,常见的 XML 解析器分别为 SAX 解析器、DOM 解析器和 PULL 解析器,下面,我将一一向大家详细介绍。
    
    SAX解析器:
    SAX(Simple API for XML)解析器是一种基于事件的解析器,它的核心是事件处理模式,主要是围绕着事件源以及事件处理器来工作的。当事件源产生事件后,调用事件处理器相应的处理方法,一个事件就可以得到处理。在事件源调用事件处理器中特定方法的时候,还要传递给事件处理器相应事件的状态信息,这样事件处理器才能够根据提供的事件信息来决定自己的行为。
    SAX解析器的优点是解析速度快,占用内存少。非常适合在Android移动设备中使用。
    
    DOM解析器:
    DOM是基于树形结构的的节点或信息片段的集合,允许开发人员使用DOM API遍历XML树、检索所需数据。分析该结构通常需要加载整个文档和构造树形结构,然后才可以检索和更新节点信息。
    由于DOM在内存中以树形结构存放,因此检索和更新效率会更高。但是对于特别大的文档,解析和加载整个文档将会很耗资源。
    
    PULL解析器:
    PULL解析器的运行方式和SAX类似,都是基于事件的模式。不同的是,在PULL解析过程中,我们需要自己获取产生的事件然后做相应的操作,而不像SAX那样由处理器触发一种事件的方法,执行我们的代码。PULL解析器小巧轻便,解析速度快,简单易用,非常适合在Android移动设备中使用,Android系统内部在解析各种XML时也是用PULL解析器。
    以上三种解析器,都是非常实用的解析器,我将会一一介绍。我们将会使用这三种解析技术完成一项共同的任务。
    
    我们新建一个项目,项目结构如下:
    
    
    
    
    
    
    我会在项目的 assets 目录中放置一个 XML 文档 books.xml,内容如下:
    
    <?xml version="1.0" encoding="utf-8"?>
    <books>
        <book>
            <id>1001</id>
            <name>Thinking In Java</name>
            <price>80.00</price>
        </book>
        <book>
            <id>1002</id>
            <name>Core Java</name>
            <price>90.00</price>
        </book>
        <book>
            <id>1003</id>
            <name>Hello, Andriod</name>
            <price>100.00</price>
        </book>
    </books>
    
    然后我们分别使用以上三种解析技术解析文档,得到一个List<Book>的对象,先来看一下Book.java的代码:
    
    public class Book {
        
        private int id;
        private String name;
        private float price;
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public float getPrice() {
            return price;
        }
    
        public void setPrice(float price) {
            this.price = price;
        }
    
        @Override
        public String toString() {
            return "id:" + id + ", name:" + name + ", price:" + price;
        }
    }
    
    最后,我们还要把这个集合对象中的数据生成一个新的XML文档,如图:
    
    
    
    生成的XML结构跟原始文档略有不同,是下面这种格式:
    
    接下来,就该介绍操作过程了,我们先为解析器定义一个 BookParser 接口,每种类型的解析器需要实现此接口。BookParser.java代码如下:
    
    public interface BookParser {
    
        /**
         * 解析输入流 得到 Book 对象集合
         * 
         * @param is
         * @return
         * @throws Exception
         */
        public List<Book> parse(InputStream is) throws Exception;
    
        /**
         * 序列化 Book 对象集合 得到 XML 形式的字符串
         * 
         * @param books
         * @return
         * @throws Exception
         */
        public String serialize(List<Book> books) throws Exception;
    }
    
    好了,我们就该一个一个的实现该接口,完成我们的解析过程。
    使用SAX解析器:
    SaxBookParser.java代码如下:
    
    public class SaxBookParser implements BookParser {
    
        @Override
        public List<Book> parse(InputStream is) throws Exception {
            
            SAXParserFactory tFactory = SAXParserFactory.newInstance(); // 取得SAXParserFactory实例
            
            SAXParser parser = tFactory.newSAXParser(); // 从factory获取SAXParser实例
            
            MyHandler tHandler = new MyHandler(); // 实例化自定义Handler
            
            parser.parse(is, tHandler); // 根据自定义Handler规则解析输入流
            
            return tHandler.getBooks();
        }
    
        @Override
        public String serialize(List<Book> books) throws Exception {
            
            SAXTransformerFactory tFactory = (SAXTransformerFactory) TransformerFactory
                    .newInstance();// 取得SAXTransformerFactory实例
            
            TransformerHandler tHandler = tFactory.newTransformerHandler(); // 从factory获取TransformerHandler实例
            Transformer tTransformer = tHandler.getTransformer(); // 从handler获取Transformer实例
            tTransformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); // 设置输出采用的编码方式
            tTransformer.setOutputProperty(OutputKeys.INDENT, "yes"); // 是否自动添加额外的空白
            tTransformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); // 是否忽略XML声明
    
            StringWriter tWriter = new StringWriter();
            Result tResult = new StreamResult(tWriter);
            tHandler.setResult(tResult);
    
            String tUri = ""; // 代表命名空间的URI 当URI无值时 须置为空字符串
            String tLocalName = ""; // 命名空间的本地名称(不包含前缀) 当没有进行命名空间处理时 须置为空字符串
    
            tHandler.startDocument();
            tHandler.startElement(tUri, tLocalName, "books", null);
    
            AttributesImpl tAttrs = new AttributesImpl(); // 负责存放元素的属性信息
            
            char[] ch = null;
            
            for (Book book : books) {
                tAttrs.clear(); // 清空属性列表
                tAttrs.addAttribute(tUri, tLocalName, "id", "string",
                        String.valueOf(book.getId()));// 添加一个名为id的属性(type影响不大,这里设为string)
                tHandler.startElement(tUri, tLocalName, "book", tAttrs); // 开始一个book元素
                                                                     // 关联上面设定的id属性
    
                tHandler.startElement(tUri, tLocalName, "name", null); // 开始一个name元素
                                                                    // 没有属性
                ch = String.valueOf(book.getName()).toCharArray();
                tHandler.characters(ch, 0, ch.length); // 设置name元素的文本节点
                tHandler.endElement(tUri, tLocalName, "name");
    
                tHandler.startElement(tUri, tLocalName, "price", null);// 开始一个price元素
                                                                    // 没有属性
                ch = String.valueOf(book.getPrice()).toCharArray();
                tHandler.characters(ch, 0, ch.length); // 设置price元素的文本节点
                tHandler.endElement(tUri, tLocalName, "price");
    
                tHandler.endElement(tUri, tLocalName, "book");
            }
            tHandler.endElement(tUri, tLocalName, "books");
            tHandler.endDocument();
    
            return tWriter.toString();
        }
    
        // 需要重写DefaultHandler的方法
        private class MyHandler extends DefaultHandler {
    
            private List<Book> mBooks;
            private Book mBook;
            private StringBuilder mBuilder;
    
            // 返回解析后得到的Book对象集合
            public List<Book> getBooks() {
    
                return mBooks;
            }
    
            @Override
            public void startDocument() throws SAXException {
                super.startDocument();
    
                mBooks = new ArrayList<Book>();
                mBuilder = new StringBuilder();
            }
    
            @Override
            public void startElement(String uri, String localName, String qName,
                    Attributes attributes) throws SAXException {
    
                super.startElement(uri, localName, qName, attributes);
    
                if (localName.equals("book")) {
    
                    mBook = new Book();
                }
    
                mBuilder.setLength(0); // 将字符长度设置为0 以便重新开始读取元素内的字符节点
            }
    
            @Override
            public void characters(char[] ch, int start, int length)
                    throws SAXException {
                super.characters(ch, start, length);
    
                mBuilder.append(ch, start, length); // 将读取的字符数组追加到builder中
            }
    
            @Override
            public void endElement(String uri, String localName, String qName)
                    throws SAXException {
                super.endElement(uri, localName, qName);
    
                if (localName.equals("id")) {
    
                    mBook.setId(Integer.parseInt(mBuilder.toString()));
                } else if (localName.equals("name")) {
    
                    mBook.setName(mBuilder.toString());
                } else if (localName.equals("price")) {
    
                    mBook.setPrice(Float.parseFloat(mBuilder.toString()));
                } else if (localName.equals("book")) {
    
                    mBooks.add(mBook);
                }
            }
        }
    }
    
    代码中,我们定义了自己的事件处理逻辑,重写了 DefaultHandler 的几个重要的事件方法。下面我为大家着重介绍一下DefaultHandler的相关知识。DefaultHandler是一个事件处理器,可以接收解析器报告的所有事件,处理所发现的数据。它实现了EntityResolver接口、DTDHandler接口、ErrorHandler接口和ContentHandler接口。这几个接口代表不同类型的事件处理器。我们着重介绍一下ContentHandler接口。结构如图:
    
    
    
    这几个比较重要的方法已被我用红线标注,DefaultHandler实现了这些方法,但在方法体内没有做任何事情,因此我们在使用时必须覆写相关的方法。最重要的是startElement方法、characters方法和endElement方法。当执行文档时遇到起始节点,startElement方法将会被调用,我们可以获取起始节点相关信息;然后characters方法被调用,我们可以获取节点内的文本信息;最后endElement方法被调用,我们可以做收尾的相关操作。
    最后,我们需要调用SAX解析程序,这个步骤在MainActivity中完成:
    
    
    使用DOM解析器:
    DomBookParser.java代码如下:
    
    public class DomBookParser implements BookParser {
    
        @Override
        public List<Book> parse(InputStream is) throws Exception {
    
            List<Book> tBooks = new ArrayList<Book>();
    
            DocumentBuilderFactory tFactory = DocumentBuilderFactory.newInstance(); // 取得
                                                                                    // DocumentBuilderFactory
            DocumentBuilder tBuilder = tFactory.newDocumentBuilder(); // 从 factory
                                                                      // 获取
                                                                      // DocumentBuilder
            Document tDocument = tBuilder.parse(is); // 解析输入流 得到 Document 实例
    
            Element tRootElement = tDocument.getDocumentElement();
            NodeList tItems = tRootElement.getElementsByTagName("book");
    
            for (int i = 0; i < tItems.getLength(); i++) {
    
                Book tBook = new Book();
                Node tItem = tItems.item(i);
                NodeList tProperties = tItem.getChildNodes();
    
                for (int j = 0; j < tProperties.getLength(); j++) {
    
                    Node tProperty = tProperties.item(j);
                    String tNodeName = tProperty.getNodeName();
    
                    if (tNodeName.equals("id")) {
    
                        tBook.setId(Integer.parseInt(tProperty.getFirstChild()
                                .getNodeValue()));
                    } else if (tNodeName.equals("name")) {
    
                        tBook.setName(tProperty.getFirstChild().getNodeValue());
                    } else if (tNodeName.equals("price")) {
    
                        tBook.setPrice(Float.parseFloat(tProperty.getFirstChild()
                                .getNodeValue()));
                    }
                }
    
                tBooks.add(tBook);
            }
            return tBooks;
        }
    
        @Override
        public String serialize(List<Book> books) throws Exception {
    
            DocumentBuilderFactory tFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder tBuilder = tFactory.newDocumentBuilder();
            Document tDocument = tBuilder.newDocument(); // 由builder创建新文档
    
            Element tRootElement = tDocument.createElement("books");
    
            for (Book book : books) {
    
                Element tBookElement = tDocument.createElement("book");
                tBookElement.setAttribute("id", book.getId() + "");
    
                Element tNameElement = tDocument.createElement("name");
                tNameElement.setTextContent(book.getName());
                tBookElement.appendChild(tNameElement);
    
                Element tPriceElement = tDocument.createElement("price");
                tPriceElement.setTextContent(book.getPrice() + "");
                tBookElement.appendChild(tPriceElement);
    
                tRootElement.appendChild(tBookElement);
            }
    
            tDocument.appendChild(tRootElement);
    
            TransformerFactory rTransFactory = TransformerFactory.newInstance();// 取得TransformerFactory实例
            Transformer tTransformer = rTransFactory.newTransformer(); // 从transFactory获取Transformer实例
            tTransformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); // 设置输出采用的编码方式
            tTransformer.setOutputProperty(OutputKeys.INDENT, "yes"); // 是否自动添加额外的空白
            tTransformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); // 是否忽略XML声明
    
            StringWriter tWriter = new StringWriter();
    
            Source tSource = new DOMSource(tDocument); // 表明文档来源是doc
            Result tResult = new StreamResult(tWriter);// 表明目标结果为writer
            tTransformer.transform(tSource, tResult); // 开始转换
    
            return tWriter.toString();
        }
    }
    
    
    
    使用PULL解析器:
    PullBookParser.java代码如下:
    
    public class PullBookParser implements BookParser {
    
        @Override
        public List<Book> parse(InputStream is) throws Exception {
    
            List<Book> tBooks = null;
            Book tBook = null;
    
            // XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
            // XmlPullParser parser = factory.newPullParser();
    
            XmlPullParser tParser = Xml.newPullParser(); // 由android.util.Xml创建一个XmlPullParser实例
            tParser.setInput(is, "UTF-8"); // 设置输入流 并指明编码方式
    
            int tEventType = tParser.getEventType();
    
            while (tEventType != XmlPullParser.END_DOCUMENT) {
    
                switch (tEventType) {
    
                case XmlPullParser.START_DOCUMENT:
    
                    tBooks = new ArrayList<Book>();
                    break;
    
                case XmlPullParser.START_TAG:
    
                    if (tParser.getName().equals("book")) {
    
                        tBook = new Book();
                    } else if (tParser.getName().equals("id")) {
    
                        tEventType = tParser.next();
                        tBook.setId(Integer.parseInt(tParser.getText()));
                    } else if (tParser.getName().equals("name")) {
    
                        tEventType = tParser.next();
                        tBook.setName(tParser.getText());
                    } else if (tParser.getName().equals("price")) {
    
                        tEventType = tParser.next();
                        tBook.setPrice(Float.parseFloat(tParser.getText()));
                    }
                    break;
                case XmlPullParser.END_TAG:
    
                    if (tParser.getName().equals("book")) {
    
                        tBooks.add(tBook);
                        tBook = null;
                    }
                    break;
                }
    
                tEventType = tParser.next();
            }
            return tBooks;
        }
    
        @Override
        public String serialize(List<Book> books) throws Exception {
    
            // XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
            // XmlSerializer serializer = factory.newSerializer();
    
            XmlSerializer tSerializer = Xml.newSerializer(); // 由android.util.Xml创建一个XmlSerializer实例
            StringWriter tWriter = new StringWriter();
    
            tSerializer.setOutput(tWriter); // 设置输出方向为writer
            tSerializer.startDocument("UTF-8", true);
            tSerializer.startTag("", "books");
    
            for (Book book : books) {
    
                tSerializer.startTag("", "book");
                tSerializer.attribute("", "id", book.getId() + "");
    
                tSerializer.startTag("", "name");
                tSerializer.text(book.getName());
                tSerializer.endTag("", "name");
    
                tSerializer.startTag("", "price");
                tSerializer.text(book.getPrice() + "");
                tSerializer.endTag("", "price");
    
                tSerializer.endTag("", "book");
            }
    
            tSerializer.endTag("", "books");
            tSerializer.endDocument();
    
            return tWriter.toString();
        }
    }
    
    最后我们看 MainActivity 方法
    public class MainActivity extends Activity implements OnClickListener {
    
        private static final String TAG = "XML";
    
        private BookParser mBookParser;
        private List<Book> mBooks;
    
        private TextView mTvXml;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            initControl();
        }
    
        /**
         * 加载控件
         */
        private void initControl() {
    
            setContentView(R.layout.activity_main);
    
            mTvXml = (TextView) this.findViewById(R.id.tvXml);
    
            Button tBtnSaxRead = (Button) this.findViewById(R.id.btnSaxRead);
            Button tBtnSaxWrite = (Button) this.findViewById(R.id.btnSaxWrite);
    
            Button tBtnDomRead = (Button) this.findViewById(R.id.btnDomRead);
            Button tBtnDomWrite = (Button) this.findViewById(R.id.btnDomWrite);
    
            Button tBtnPullRead = (Button) this.findViewById(R.id.btnPullRead);
            Button tBtnPullWrite = (Button) this.findViewById(R.id.btnPullWrite);
    
            tBtnSaxRead.setOnClickListener(this);
            tBtnSaxWrite.setOnClickListener(this);
    
            tBtnDomRead.setOnClickListener(this);
            tBtnDomWrite.setOnClickListener(this);
    
            tBtnPullRead.setOnClickListener(this);
            tBtnPullWrite.setOnClickListener(this);
        }
    
        @Override
        public void onClick(View v) {
    
            switch (v.getId()) {
            case R.id.btnSaxRead: // Sax读取
    
                mBookParser = new SaxBookParser(); // 创建SaxBookParser实例
                read("Sax读取:");
    
                break;
    
            case R.id.btnSaxWrite:
    
                mBookParser = new SaxBookParser(); // 创建SaxBookParser实例
                write();
    
                break;
            case R.id.btnDomRead:
    
                mBookParser = new DomBookParser(); // 创建DomBookParser实例
                read("Dom读取:");
    
                break;
            case R.id.btnDomWrite:
    
                mBookParser = new DomBookParser(); // 创建DomBookParser实例
                write();
    
                break;
            case R.id.btnPullRead:
    
                mBookParser = new PullBookParser(); // 创建PullBookParser实例
                read("Pull读取:");
    
                break;
            case R.id.btnPullWrite:
    
                mBookParser = new PullBookParser(); // 创建PullBookParser实例
                write();
    
                break;
            }
        }
    
        /**
         * 读取
         */
        private void read(String title) {
    
            StringBuffer tXmlContent = new StringBuffer();
            tXmlContent.append(title);
            try {
    
                InputStream tInputStream = getAssets().open("books.xml");
                mBooks = mBookParser.parse(tInputStream); // 解析输入流
    
                for (Book book : mBooks) {
    
                    Log.i(TAG, book.toString());
                    tXmlContent.append(book.toString());
                    tXmlContent.append("—");
                }
    
                mTvXml.setText(tXmlContent.toString());
            } catch (Exception e) {
    
                Log.e(TAG, e.getMessage());
            }
        }
    
        /**
         * 写入
         */
        private void write() {
    
            try {
    
                String xml = mBookParser.serialize(mBooks); // 序列化
    
                FileOutputStream tFileOutputStream = openFileOutput("books.xml",
                        Context.MODE_PRIVATE);
    
                tFileOutputStream.write(xml.getBytes("UTF-8"));
    
            } catch (Exception e) {
                Log.e(TAG, e.getMessage());
            }
        }
    }
    
    【运行结果】
  • 相关阅读:
    sys.exc_info()方法:获取异常信息
    tempfile模块:生成临时文件和临时目录
    fnmatch模块:用于文件名的匹配
    pathlib模块用法详解
    linecache模块 随机读取文件指定行
    fileinput模块:逐行读取多个文件
    asyncio异步IO--协程(Coroutine)与任务(Task)详解
    Python中协程异步IO(asyncio)详解
    删除某个时间段之前的文件
    Mac入门--如何使用brew安装多个PHP版本
  • 原文地址:https://www.cnblogs.com/zx-blog/p/11836196.html
Copyright © 2020-2023  润新知