如何使用 SAX 解析器解析 XML

Posted

技术标签:

【中文标题】如何使用 SAX 解析器解析 XML【英文标题】:How to parse XML using the SAX parser 【发布时间】:2011-06-17 04:11:02 【问题描述】:

我正在关注这个tutorial。

效果很好,但我希望它返回一个包含所有字符串的数组,而不是一个包含最后一个元素的字符串。

任何想法如何做到这一点?

【问题讨论】:

能否请您发布您的 XML 结构的抽象描述? dearfriends.se/category/blog/feed/rss -> 查看源代码 【参考方案1】:

所以你想构建一个 XML 解析器来解析像这样的 RSS 提要。

<rss version="0.92">
<channel>
    <title>MyTitle</title>
    <link>http://myurl.com</link>
    <description>MyDescription</description>
    <lastBuildDate>SomeDate</lastBuildDate>
    <docs>http://someurl.com</docs>
    <language>SomeLanguage</language>

    <item>
        <title>TitleOne</title>
        <description><![CDATA[Some text.]]></description>
        <link>http://linktoarticle.com</link>
    </item>

    <item>
        <title>TitleTwo</title>
        <description><![CDATA[Some other text.]]></description>
        <link>http://linktoanotherarticle.com</link>
    </item>

</channel>
</rss>

现在您有两个可以使用的 SAX 实现。您可以使用org.xml.saxandroid.sax 实现。在发布一个短手示例后,我将解释两者的优缺点。

android.sax 实现

让我们从android.sax 实现开始。

您首先必须使用RootElementElement 对象定义XML 结构。

无论如何,我都会使用 POJO(普通旧 Java 对象)来保存您的数据。这将是所需的 POJO。

Channel.java

public class Channel implements Serializable 

    private Items items;
    private String title;
    private String link;
    private String description;
    private String lastBuildDate;
    private String docs;
    private String language;

    public Channel() 
        setItems(null);
        setTitle(null);
        // set every field to null in the constructor
    

    public void setItems(Items items) 
        this.items = items;
    

    public Items getItems() 
        return items;
    

    public void setTitle(String title) 
        this.title = title;
    

    public String getTitle() 
        return title;
    
    // rest of the class looks similar so just setters and getters

该类实现了Serializable 接口,因此您可以将其放入Bundle 并对其进行处理。

现在我们需要一个类来保存我们的项目。在这种情况下,我将扩展 ArrayList 类。

Items.java

public class Items extends ArrayList<Item> 

    public Items() 
        super();
    


这就是我们的物品容器。我们现在需要一个类来保存每个项目的数据。

Item.java

public class Item implements Serializable 

    private String title;
    private String description;
    private String link;

    public Item() 
        setTitle(null);
        setDescription(null);
        setLink(null);
    

    public void setTitle(String title) 
        this.title = title;
    

    public String getTitle() 
        return title;
    

    // same as above.


例子:

public class Example extends DefaultHandler 

    private Channel channel;
    private Items items;
    private Item item;

    public Example() 
        items = new Items();
    

    public Channel parse(InputStream is) 
        RootElement root = new RootElement("rss");
        Element chanElement = root.getChild("channel");
        Element chanTitle = chanElement.getChild("title");
        Element chanLink = chanElement.getChild("link");
        Element chanDescription = chanElement.getChild("description");
        Element chanLastBuildDate = chanElement.getChild("lastBuildDate");
        Element chanDocs = chanElement.getChild("docs");
        Element chanLanguage = chanElement.getChild("language");

        Element chanItem = chanElement.getChild("item");
        Element itemTitle = chanItem.getChild("title");
        Element itemDescription = chanItem.getChild("description");
        Element itemLink = chanItem.getChild("link");

        chanElement.setStartElementListener(new StartElementListener() 
            public void start(Attributes attributes) 
                channel = new Channel();
            
        );

        // Listen for the end of a text element and set the text as our
        // channel's title.
        chanTitle.setEndTextElementListener(new EndTextElementListener() 
            public void end(String body) 
                channel.setTitle(body);
            
        );

        // Same thing happens for the other elements of channel ex.

        // On every <item> tag occurrence we create a new Item object.
        chanItem.setStartElementListener(new StartElementListener() 
            public void start(Attributes attributes) 
                item = new Item();
            
        );

        // On every </item> tag occurrence we add the current Item object
        // to the Items container.
        chanItem.setEndElementListener(new EndElementListener() 
            public void end() 
                items.add(item);
            
        );

        itemTitle.setEndTextElementListener(new EndTextElementListener() 
            public void end(String body) 
                item.setTitle(body);
            
        );

        // and so on

        // here we actually parse the InputStream and return the resulting
        // Channel object.
        try 
            Xml.parse(is, Xml.Encoding.UTF_8, root.getContentHandler());
            return channel;
         catch (SAXException e) 
            // handle the exception
         catch (IOException e) 
            // handle the exception
        

        return null;
    


如您所见,这是一个非常简单的示例。使用android.sax SAX 实现的主要优点是您可以定义必须解析的XML 的结构,然后只需将事件侦听器添加到适当的元素。缺点是代码非常重复和臃肿。

org.xml.sax 实施

org.xml.sax SAX 处理程序实现有点不同。

这里您没有指定或声明您的 XML 结构,而只是监听事件。最广泛使用的是以下事件:

文档开始 文档结束 元素开始 元素结束 元素开始和元素结束之间的字符

使用上面的 Channel 对象的示例处理程序实现如下所示。

例子

public class ExampleHandler extends DefaultHandler 

    private Channel channel;
    private Items items;
    private Item item;
    private boolean inItem = false;

    private StringBuilder content;

    public ExampleHandler() 
        items = new Items();
        content = new StringBuilder();
    

    public void startElement(String uri, String localName, String qName, 
            Attributes atts) throws SAXException 
        content = new StringBuilder();
        if(localName.equalsIgnoreCase("channel")) 
            channel = new Channel();
         else if(localName.equalsIgnoreCase("item")) 
            inItem = true;
            item = new Item();
        
    

    public void endElement(String uri, String localName, String qName) 
            throws SAXException 
        if(localName.equalsIgnoreCase("title")) 
            if(inItem) 
                item.setTitle(content.toString());
             else 
                channel.setTitle(content.toString());
            
         else if(localName.equalsIgnoreCase("link")) 
            if(inItem) 
                item.setLink(content.toString());
             else 
                channel.setLink(content.toString());
            
         else if(localName.equalsIgnoreCase("description")) 
            if(inItem) 
                item.setDescription(content.toString());
             else 
                channel.setDescription(content.toString());
            
         else if(localName.equalsIgnoreCase("lastBuildDate")) 
            channel.setLastBuildDate(content.toString());
         else if(localName.equalsIgnoreCase("docs")) 
            channel.setDocs(content.toString());
         else if(localName.equalsIgnoreCase("language")) 
            channel.setLanguage(content.toString());
         else if(localName.equalsIgnoreCase("item")) 
            inItem = false;
            items.add(item);
         else if(localName.equalsIgnoreCase("channel")) 
            channel.setItems(items);
        
    

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

    public void endDocument() throws SAXException 
        // you can do something here for example send
        // the Channel object somewhere or whatever.
    


老实说,我无法真正告诉您这个处理程序实现相对于android.sax 的任何真正优势。然而,我可以告诉你现在应该很明显的缺点。看看startElement 方法中的else if 语句。由于我们有标签&lt;title&gt;linkdescription,因此我们必须在目前的XML 结构中进行跟踪。也就是说,如果我们遇到&lt;item&gt; 起始标签,我们将inItem 标志设置为true,以确保我们将正确的数据映射到正确的对象,并且在endElement 方法中,我们将该标志设置为false,如果我们遇到了&lt;/item&gt; 标签。表示我们已完成该项目标签。

在这个例子中,管理起来很容易,但是必须解析一个具有不同级别的重复标签的更复杂的结构变得很棘手。例如,您必须使用 Enums 来设置当前状态,并使用许多 switch/case 语句来检查您的位置,或者更优雅的解决方案是使用标签堆栈的某种标签跟踪器。

【讨论】:

@Adinia 请注意,同时使用这两种实现是没有问题的。只要你知道你为什么这样做,这样做是没有问题的。 @octavian-damiean 我的代码确实可以运行,但我真的不知道我为什么要写每一行;我现在尝试清理一下,因为我了解它们每个人的工作方式。所以感谢您的注意,可以同时使用两者。 @Adinia 我明白了。不客气。如果您对此有更多疑问,也可以加入coolAndroid chat room。 @OctavianDamiean(抱歉之前的评论,5分钟后无法编辑)您知道是否有任何方法可以使用android.sax解析器区分不同的XML根元素?例如,如果我想以通用方式解析 RSS 或 Atom 提要(即让解析器了解这两种格式并选择正确的格式),我能否使用相同的 ContentHandler 同时处理 根元素?或者我是否需要为此目的使用普通的 org.xml.sax? 是的,你可以。您需要一个通用处理程序,它只读取第一个元素(根元素)并委托给下一个适当的处理程序。【参考方案2】:

在许多问题中,有必要为不同的目的使用不同类型的 xml 文件。我不会试图去把握这浩瀚,并从我自己的经验中说出我需要这一切。

Java,也许是我最喜欢的编程语言。此外,您可以解决任何问题并且不需要骑自行车,从而加强了这种爱。

因此,我创建了一堆运行数据库的客户端-服务器,允许客户端远程在数据库服务器中创建条目。无需检查输入数据等,但并非如此。

作为工作原则,我毫不犹豫的选择了以xml文件的形式传输信息。以下类型:

<? xml version = "1.0" encoding = "UTF-8" standalone = "no"?> 
<doc> 
<id> 3 </ id> 
<fam> Ivanov </ fam> 
<name> Ivan </ name> 
<otc> I. </ otc> 
<dateb> 10-03-2005 </ dateb> 
<datep> 10-03-2005 </ datep> 
<datev> 10-03-2005 </ datev> 
<datebegin> 09-06-2009 </ datebegin> 
<dateend> 10-03-2005 </ dateend> 
<vdolid> 1 </ vdolid> 
<specid> 1 </ specid> 
<klavid> 1 </ klavid> 
<stav> 2.0 </ stav> 
<progid> 1 </ progid> 
</ doc> 

除了说它是关于医生机构的信息外,让它更易于阅读。姓氏、名字、唯一 ID 等。一般来说,数据系列。该文件安全到达服务器端,然后开始解析该文件。

在解析的两个选项中(SAX vs DOM)我选择了SAX 是因为他的工作比较聪明,而且他是我第一个落入手中的:)

所以。如您所知,要成功使用解析器,我们需要覆盖 DefaultHandler 所需的方法。首先,连接所需的包。

import org.xml.sax.helpers.DefaultHandler; 
import org.xml.sax. *; 

现在我们可以开始编写解析器了

public class SAXPars extends DefaultHandler 
   ... 
 

让我们从方法startDocument()开始。顾名思义,他对文档开头的事件做出反应。在这里你可以挂起各种动作,比如内存分配,或者重置值​​​​,但是我们的例子很简单,所以只需标记一个适当消息的开始工作:

Override 
public void startDocument () throws SAXException 
   System.out.println ("Start parse XML ..."); 
 

接下来。解析器通过文档遇到其结构的元素。启动方法 startElement()。而事实上,他的出现是这样的:startElement(String namespaceURI, String localName, String qName, Attributes atts)。这里 namespaceURI - 命名空间,localName - 元素的本地名称,qName - 本地名称与命名空间(用冒号分隔)和 atts - 此元素的属性的组合。在这种情况下,一切都很简单。使用 qName'om 并将其放入一些服务行 thisElement 就足够了。因此,我们标记了当前我们所在的元素。

@Override 
public void startElement (String namespaceURI, String localName, String qName, Attributes atts) throws SAXException 
   thisElement = qName; 
 

接下来,我们了解一下会议项目的含义。这里包括方法字符()。他的形式是:字符(char [] ch, int start, int length)。那么这里一切都清楚了。 ch - 包含字符串本身在此元素中的自我重要性的文件。 start 和 length - 服务的数量,表示线路和长度的起点。

@Override 
public void characters (char [] ch, int start, int length) throws SAXException 
   if (thisElement.equals ("id")) 
      doc.setId (new Integer (new String (ch, start, length))); 
    
   if (thisElement.equals ("fam")) 
      doc.setFam (new String (ch, start, length)); 
    
   if (thisElement.equals ("name")) 
      doc.setName (new String (ch, start, length)); 
    
   if (thisElement.equals ("otc")) 
      doc.setOtc (new String (ch, start, length)); 
    
   if (thisElement.equals ("dateb")) 
      doc.setDateb (new String (ch, start, length)); 
    
   if (thisElement.equals ("datep")) 
      doc.setDatep (new String (ch, start, length)); 
    
   if (thisElement.equals ("datev")) 
      doc.setDatev (new String (ch, start, length)); 
    
   if (thisElement.equals ("datebegin")) 
      doc.setDatebegin (new String (ch, start, length)); 
    
   if (thisElement.equals ("dateend")) 
      doc.setDateend (new String (ch, start, length)); 
    
   if (thisElement.equals ("vdolid")) 
      doc.setVdolid (new Integer (new String (ch, start, length))); 
    
   if (thisElement.equals ("specid")) 
      doc.setSpecid (new Integer (new String (ch, start, length))); 
    
   if (thisElement.equals ("klavid")) 
      doc.setKlavid (new Integer (new String (ch, start, length))); 
    
   if (thisElement.equals ("stav")) 
      doc.setStav (new Float (new String (ch, start, length))); 
    
   if (thisElement.equals ("progid")) 
      doc.setProgid (new Integer (new String (ch, start, length))); 
    
 

啊,是的。我差点忘了。作为其对象将折叠 naparsennye 数据说话医生的类型。此类已定义并具有所有必要的 setter-getter。

下一个明显的元素结束,然后是下一个。负责结束endElement()。它向我们表明该项目已结束,此时您可以做任何事情。将进行。清洁元素。

@Override 
public void endElement (String namespaceURI, String localName, String qName) throws SAXException 
   thisElement = ""; 
 

来到整个文件,我们来到文件的末尾。工作结束文档()。在里面,我们可以释放内存,做一些诊断和suyu打印等。在我们的例子中,只写解析结束的内容。

@Override 
public void endDocument () 
   System.out.println ("Stop parse XML ..."); 
 

所以我们有一个类来解析 xml 我们的格式。以下为全文:

import org.xml.sax.helpers.DefaultHandler; 
import org.xml.sax. *; 
 
public class SAXPars extends DefaultHandler 
 
Doctors doc = new Doctors (); 
String thisElement = ""; 
 
public Doctors getResult () 
   return doc; 
 
 
@Override 
public void startDocument () throws SAXException 
   System.out.println ("Start parse XML ..."); 
 
 
@Override 
public void startElement (String namespaceURI, String localName, String qName, Attributes atts) throws SAXException 
   thisElement = qName; 
 
 
@Override 
public void endElement (String namespaceURI, String localName, String qName) throws SAXException 
   thisElement = ""; 
 
 
@Override 
public void characters (char [] ch, int start, int length) throws SAXException 
   if (thisElement.equals ("id")) 
      doc.setId (new Integer (new String (ch, start, length))); 
    
   if (thisElement.equals ("fam")) 
      doc.setFam (new String (ch, start, length)); 
    
   if (thisElement.equals ("name")) 
      doc.setName (new String (ch, start, length)); 
    
   if (thisElement.equals ("otc")) 
      doc.setOtc (new String (ch, start, length)); 
    
   if (thisElement.equals ("dateb")) 
      doc.setDateb (new String (ch, start, length)); 
    
   if (thisElement.equals ("datep")) 
      doc.setDatep (new String (ch, start, length)); 
    
   if (thisElement.equals ("datev")) 
      doc.setDatev (new String (ch, start, length)); 
    
   if (thisElement.equals ("datebegin")) 
      doc.setDatebegin (new String (ch, start, length)); 
    
   if (thisElement.equals ("dateend")) 
      doc.setDateend (new String (ch, start, length)); 
    
   if (thisElement.equals ("vdolid")) 
      doc.setVdolid (new Integer (new String (ch, start, length))); 
    
   if (thisElement.equals ("specid")) 
      doc.setSpecid (new Integer (new String (ch, start, length))); 
    
   if (thisElement.equals ("klavid")) 
      doc.setKlavid (new Integer (new String (ch, start, length))); 
    
   if (thisElement.equals ("stav")) 
      doc.setStav (new Float (new String (ch, start, length))); 
    
   if (thisElement.equals ("progid")) 
      doc.setProgid (new Integer (new String (ch, start, length))); 
    
 
 
@Override 
public void endDocument () 
   System.out.println ("Stop parse XML ..."); 
 
 

我希望该主题有助于轻松介绍 SAX 解析器的本质。

不要严格判断第一篇文章 :) 我希望它至少对某人有用。

UPD:要运行此解析器,您可以使用以下代码:

SAXParserFactory factory = SAXParserFactory.newInstance (); 
SAXParser parser = factory.newSAXParser (); 
SAXPars saxp = new SAXPars (); 
 
parser.parse (new File ("..."), saxp); 

【讨论】:

【参考方案3】:
public class MainActivity extends AppCompatActivity 
   ListView lvPcsPost;
    ArrayList<String> name;
    ArrayList<String> price;
    ArrayList<String> Description;
    LayoutInflater layoutInflater;
    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        lvPcsPost = (ListView) findViewById(R.id.lvPcsPost);
        name = new ArrayList<>();
        price = new ArrayList<>();
        Description = new ArrayList<>();
        new PostAsync().execute();
    

    class PostAsync extends AsyncTask<Void, Void, Void> 
        ProgressDialog pd;
        XMLHelper helper;


        @Override
        protected void onPreExecute() 
            pd = ProgressDialog.show(MainActivity.this, "", "Loading...", true, false);
        

        @Override
        protected Void doInBackground(Void... arg0) 
            helper = new XMLHelper();
            helper.get();
            return null;
        

        @Override
        protected void onPostExecute(Void result) 
            PostBaseAdapter postBaseAdapter = new PostBaseAdapter();
            lvPcsPost.setAdapter(postBaseAdapter);
            pd.dismiss();
        

    

    public class XMLHelper extends DefaultHandler 

        private String URL_MAIN = "http://uat.winitsoftware.com/ThemeManager/Data/Products/Products.xml";
        String TAG = "XMLHelper";

        Boolean currTag = false;
        String currTagVal = "";

        public void get() 
            try 
                SAXParserFactory factory = SAXParserFactory.newInstance();
                SAXParser mSaxParser = factory.newSAXParser();
                XMLReader mXmlReader = mSaxParser.getXMLReader();
                mXmlReader.setContentHandler(this);
                InputStream mInputStream = new URL(URL_MAIN).openStream();
                mXmlReader.parse(new InputSource(mInputStream));
             catch (Exception e) 
                Log.e(TAG, "Exception: " + e.getMessage());
            
        

        @Override
        public void characters(char[] ch, int start, int length)
                throws SAXException 
            if (currTag) 
                currTagVal = currTagVal + new String(ch, start, length);
                currTag = false;
            
        

        @Override
        public void endElement(String uri, String localName, String qName)
                throws SAXException 
            currTag = false;

            if (localName.equalsIgnoreCase("Name"))
                name.add(currTagVal);

            else if (localName.equalsIgnoreCase("Description"))
             Description.add(currTagVal);

            else if (localName.equalsIgnoreCase("Price"))
              price.add(currTagVal);

        
        @Override
        public void startElement(String uri, String localName, String qName,
                                 Attributes attributes) throws SAXException 
            Log.i(TAG, "TAG: " + localName);

            currTag = true;
            currTagVal = "";
            if (localName.equals("Products"));
        
    

    public class PostBaseAdapter extends BaseAdapter 

        public PostBaseAdapter() 

        

        @Override
        public int getCount() 
            return name.size();
        

        @Override
        public Object getItem(int position) 
            return name.get(position);
        

        @Override
        public long getItemId(int position) 
            return 0;
        

        @Override
        public View getView(int position, View convertView, ViewGroup parent) 

            layoutInflater = LayoutInflater.from(getApplicationContext());

            convertView = layoutInflater.inflate(R.layout.list_item_post, parent, false);
            TextView  txtPrice = (TextView) convertView.findViewById(R.id.txtPrice);
            TextView  txtDescription = (TextView) convertView.findViewById(R.id.txtDescription);
            TextView   txtName = (TextView) convertView.findViewById(R.id.txtName);
            ImageView   image = (ImageView) convertView.findViewById(R.id.Image);
            ImageView  bigImage = (ImageView) convertView.findViewById(R.id.BigImage);

                txtPrice.setText("Price : "+price.get(position));
                 txtDescription.setText("Description : "+Description.get(position));
                txtName.setText("Name : "+name.get(position));

            return convertView;
        
    

【讨论】:

以上是关于如何使用 SAX 解析器解析 XML的主要内容,如果未能解决你的问题,请参考以下文章

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

如何让 SAX 解析器从 xml 声明中确定编码?

如何在 android 中使用 DOM 或 SAX 解析器从 XML 读取子节点

jaxp解析器——sax

如何在Ruby on Rails上使用SAX解析器来处理大型XML文件

使用 SAX 解析器时如何获取父节点?