Xml解析之Sax解析(传入xml即可得到实体类集合)

7 篇文章 1 订阅


之前想写一个JAXB解析xml与实体类转换的,但是发现JAXB有一定的局限性,有时,在解析非标准xml中的属性值时,不能够获取到其中的值,很奇怪的是,JAXB是jdk中自带的API,竟然在AndroidStudio环境中竟然不能使用,引入jar包也会报错,后索性改为用SAX解析,并对其进行了一定的封装,只需要传入几个简单的参数即可得到想要的实体类。

如果你的需求是根据解析xml返回一个简单对象集合,那么来这就对了。

何为简单对象,即这个对象的成员类型属于基本数据类型,当然了Date也可以,你只需要添加相关注解将字符串转换成date就行了;不含有自定义类

1. 简单介绍SAX

SAX(Simple API for XML)解析器是一种基于事件的解析器,它的核心是事件处理模式,主要是围绕着事件源以及事件处理器来工作的。当事件源产生事件后,调用事件处理器相应的处理方法,一个事件就可以得到处理。在事件源调用事件处理器中特定方法的时候,还要传递给事件处理器相应事件的状态信息,这样事件处理器才能够根据提供的事件信息来决定自己的行为。
SAX解析器的优点是解析速度快,占用内存少。非常适合在Android移动设备中使用

2. 列出要解析的数据(数据来源:http://wcf.open.cnblogs.com/news/hot/10)

<feed xmlns="http://www.w3.org/2005/Atom">
<title type="text">博客园新闻频道</title>
<id>uuid:400dd255-fe1f-4cc3-bebd-bbe2e47f2c0f;id=49744</id>
<updated>2017-04-09T10:13:38Z</updated>
<link href="http://news.cnblogs.com/" />
<entry>
<id>566495</id>
<title type="text">老外两张漫画实力黑Linux版SQL Server</title>
<summary type="text">
Linux 版 SQL Server(一)在 Linux 内核之中,大家正在静静的等待进程的创建。每个创建的进程会被分配一个进程 ID (PID)。在那个 Apache 进程高高兴兴的走出去之后,下一位却被要求创建 Linux 版的 SQL Server,这简直让人气的跳...
</summary>
<published>2017-04-06T09:22:33+08:00</published>
<updated>2017-04-09T10:13:38Z</updated>
<link rel="alternate" href="http://news.cnblogs.com/n/566495/" />
<diggs>16</diggs>
<views>4542</views>
<comments>20</comments>
<topic />
<topicIcon>http://images0.cnblogs.com/news_topic///images0.cnblogs.com/news_topic/sqlserver.gif</topicIcon>
<sourceName>linux.cn</sourceName>
</entry>
<entry>
<id>566335</id>
<title type="text">IBM都叫停SOHO办公了!创业公司还要犯这大忌?</title>
<summary type="text">
SOHO 办公一度是个十分流行的概念。据美国民意调查机构 Gallup poll 统计,美国每四个人中就有一个人选择 SOHO 办公。中国创业者最崇拜的就是自由式、咖啡厅式的谷歌办公环境。但现在,即使是一些以创新和开放著称的大公司,也开始逐渐召回自己的 SOHO 员工,让他们...
</summary>
<published>2017-04-04T16:50:41+08:00</published>
<updated>2017-04-09T10:13:38Z</updated>
<link rel="alternate" href="http://news.cnblogs.com/n/566335/" />
<diggs>6</diggs>
<views>3866</views>
<comments>11</comments>
<topic />
<topicIcon>http://images0.cnblogs.com/news_topic///images0.cnblogs.com/news_topic/ibm.gif</topicIcon>
<sourceName>虎嗅网</sourceName>
</entry>
</feed>

3. 从上面数据我们可以看出,主要是要entry中的值,所以根据entry内容,我们得到以下实体类:

/**
 * Created by zml2015 on 2017/4/8 20:16.
 */

public class BKYNews {

    private String id;

    private String title;

    /**
     * 总结,新闻概述
     */
    private String summary;

    /**
     * 发布时间
     */
    private String published;

    /**
     * 更新时间
     */
    private String updated;

    private String link_href;
    /**
     * 推荐次数(点赞次数)
     */
    private String diggs;
    /**
     * 浏览量
     */
    private String views;

    /**
     * 评论数
     */
    private String comments;

    /**
     * 话题图标,这里的网址有问题,这里的网址有一部分是重复的,只需要截取“///”之后的内容然后前篇拼接“http://”即可
     */
    private String topicIcon;

    /**
     * 新闻来源
     */
    private String sourceName;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

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

    public String getSummary() {
        return summary;
    }

    public void setSummary(String summary) {
        this.summary = summary;
    }

    public String getPublished() {
        return published;
    }

    public void setPublished(String published) {
        this.published = published;
    }

    public String getUpdated() {
        return updated;
    }

    public void setUpdated(String updated) {
        this.updated = updated;
    }


    public String getDiggs() {
        return diggs;
    }

    public void setDiggs(String diggs) {
        this.diggs = diggs;
    }

    public String getViews() {
        return views;
    }

    public void setViews(String views) {
        this.views = views;
    }

    public String getComments() {
        return comments;
    }

    public void setComments(String comments) {
        this.comments = comments;
    }

    public String getTopicIcon() {
        return topicIcon;
    }

    public void setTopicIcon(String topicIcon) {
        this.topicIcon = topicIcon;
    }

    public String getSourceName() {
        return sourceName;
    }

    public void setSourceName(String sourceName) {
        this.sourceName = sourceName;
    }

    public String getLink_href() {
		return link_href;
	}

	public void setLink_href(String link_href) {
		this.link_href = link_href;
	}

	@Override
    public String toString() {
        return "BKYNews{" +
                "id='" + id + '\'' +
                ", title='" + title + '\'' +
                ", summary='" + summary + '\'' +
                ", published='" + published + '\'' +
                ", updated='" + updated + '\'' +
                ", link='" + link_href + '\'' +
                ", diggs='" + diggs + '\'' +
                ", views='" + views + '\'' +
                ", comments='" + comments + '\'' +
                ", topicIcon='" + topicIcon + '\'' +
                ", sourceName='" + sourceName + '\'' +
                '}';
    }
}

4. 给出的实体类的规则是:

* 属性名与要解析的xml的标签名一致
* 如果要解析标签中的属性值,则命名规则为:标签名+下划线+ 属性名
> 如上面给出的实体类中一个属性名叫`link_href`,即是xml中link标签中的href属性

* 暂只支持标签2层`(要解析的数据有两层,而不是要解析的数据在整个xml数据中有多少层)`嵌套,不支持标签多层嵌套,(如entry标签中有author,author标签中有name),稍后我会尝试写一个,如有大神解救,甚是欢喜
* 实体类名称与要解析的标签名称不一致没有关系,因为需要将要解析的根标签传入,如,我们要解析的数据是entry标签中的内容,所以要传入entry

5. 给出SAX最最重要的解析过程,这个是个工具类中的内容,可直接拷贝,无须修改

/**
 * @author zml2015
 * @Email zhengmingliang911@gmail.com
 * @Time 2017年4月9日 下午2:58:36
 * @Description <p>用于解析xml为实体类的处理器  </P>
 * @version 1.0  
 */
class XMlHandler<T> extends DefaultHandler{
	String rootElemntName;
	Map<String, String> dataMap;
	StringBuilder stringBuilder;
	List<T> dataList;
	T data;
	Class<T> clz;
	private Map<String,Class> attrs;
	
	/**
	 * @author zml2015
	 * @Email zhengmingliang911@gmail.com
	 * @Time 2017年4月9日 下午3:13:43
	 * @Description <p> 当前标签名称 </P>
	 * @version 1.0  
	 */
	private String currentTag;
	/**
	 * 要解析的单个实体的根元素名称
	 * @param rootElemntName
	 * @throws IllegalAccessException 
	 * @throws InstantiationException 
	 */
	XMlHandler(String rootElemntName,Class<T> clz,Map<String,Class> attrsMap) {
		this.rootElemntName = rootElemntName;
		this.clz = clz;
		this.attrs = attrsMap;
	}
	
	@Override
	public void startDocument() throws SAXException {
		super.startDocument();
		dataMap = new HashMap<String, String>();
		stringBuilder = new StringBuilder();
		dataList = new ArrayList<>();
	}
	
	@Override
	public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
		// TODO Auto-generated method stub
		super.startElement(uri, localName, qName, attributes);
		//赋值给当前标签名称
		currentTag = qName;
		if (rootElemntName.equals(qName)) {
			try {
				data = clz.newInstance();
				
			} catch (InstantiationException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
		}
		//每次对一个标签解析前,都先置为空
		stringBuilder.setLength(0);
		//如果某个标签中有属性,则将其保存到map中,保存规则:key = “标签名称_属性名称” value = “属性值”
		if(attributes != null && dataMap != null){
			for (int i = 0; i < attributes.getLength(); i++) {
				dataMap.put(qName+"_"+attributes.getQName(i), attributes.getValue(i));
			}
			
		}
		
		
	}
	
	@Override
	public void characters(char[] ch, int start, int len) throws SAXException {
		// TODO Auto-generated method stub
		super.characters(ch, start, len);
		stringBuilder.append(ch,start,len);
		Field[] fields = clz.getDeclaredFields();
		try {
			if (StringUtils.isNotEmpty(currentTag) && data != null) {
				for (Field field : fields) {
					String name = field.getName();
					if (currentTag.equals(name)) {
						name = name.substring(0,1).toUpperCase() + name.substring(1);
						
						Method method = data.getClass().getMethod("set"+name, field.getType());
						method.invoke(data, stringBuilder.toString());
					}
				}
			}
		
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}

	@Override
	public void endElement(String uri, String localName, String qName) throws SAXException {
        super.endElement(uri, localName, qName);
        if (rootElemntName.equals(qName)) {
        	try {
				if (attrs != null) {
					
					for (String attrName : attrs.keySet()) {
						String methodName = "set"+attrName.substring(0,1).toUpperCase()+attrName.substring(1);
						Method method = data.getClass().getMethod(methodName,attrs.get(attrName));
						method.invoke(data, dataMap.get(attrName));
					}
				}
			} catch (NoSuchMethodException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (SecurityException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalArgumentException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
        	
			dataList.add(data);
		}
        
	}

	@Override
	public void endDocument() throws SAXException {
	System.out.println("解析结束:"+dataList);
		super.endDocument();
	}


	public List<T> getDataList(){
		return dataList;
	}
	
	public T getData(){
		return data;
	}
}

6. 工具类:主要看工具类的最后一个方法:parseXml2Bean,前面方法均是JAXB对xml与对象转换的方法

import java.io.ByteArrayInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

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

import top.wys.developerclub.model.SysUser;
import top.wys.developerclub.test.BKYAticle;

public class XmlUtils {


	/**
	 * @author zml2015
	 * @Time 2017年4月1日19:04:50
	 * @Description <p>
	 *              将实体类直接转换成xml
	 *              </p>
	 * @param t
	 *            要转换成xml的对象
	 * @return xml字符串
	 */
	public static <T> String createXmlFromBean(T t) {
		String xml = "";
		if (t == null) {
			return xml;
		} else {
			try (StringWriter write = new StringWriter()) {
				JAXBContext context = JAXBContext.newInstance(t.getClass());
				Marshaller marshaller = context.createMarshaller();
				marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
				marshaller.marshal(t, write);
				xml = write.toString();
			} catch (JAXBException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			return xml;
		}
	}
	/**
	 * @author zml2015
	 * @Time 2017年4月1日19:04:50
	 * @Description <p>
	 *              将实体类直接转换成xml,要转换的类必需加油@XmlRootElement注解
	 *              </p>
	 * @param t
	 *            要转换成xml的对象
	 * @param format 是否格式化输出,{@code true}格式化输出,{@code false} 不进行格式化
	 * @return xml字符串
	 */
	public static <T> String createXmlFromBean(T t,boolean format) {
		String xml = "";
		if (t == null) {
			return xml;
		} else {
			try (StringWriter write = new StringWriter()) {
				// 利用jdk中自带的转换类实现
				JAXBContext context = JAXBContext.newInstance(t.getClass());
				Marshaller marshaller = context.createMarshaller();
				// 格式化xml输出的格式 
				marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, format);
				// 将对象转换成xml写入到StringWriter中
				marshaller.marshal(t, write);
				xml = write.toString();
			} catch (JAXBException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			return xml;
		}
	}
	/**
	 * @author zml2015
	 * @Time 2017年4月1日19:04:50
	 * @Description <p>
	 *              将实体类直接转换成xml,要转换的类必需加油@XmlRootElement注解
	 *              </p>
	 * @param t	要转换成xml的对象
	 *            
	 * @param filePath 要保存到的文件路径
	 * @return xml字符串
	 */
	public static <T> void createXmlToFile(T t,String filePath) {
			try (FileWriter write = new FileWriter(filePath)) {
				// 利用jdk中自带的转换类实现
				JAXBContext context = JAXBContext.newInstance(t.getClass());
				Marshaller marshaller = context.createMarshaller();
				// 格式化xml输出的格式 
				marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
				// 将对象转换成xml写入到StringWriter中
				marshaller.marshal(t, write);
			} catch (JAXBException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
	}

	@SuppressWarnings("unchecked")
	public static <T> T getBeanFromXml(String xml, Class<T> clz) {
		T t = null;
		try (StringReader reader = new StringReader(xml);) {
			JAXBContext context = JAXBContext.newInstance(clz);
			Unmarshaller unmarshal = context.createUnmarshaller();
			t = (T) unmarshal.unmarshal(reader);
		} catch (JAXBException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return t;
	}
	
	@SuppressWarnings("unchecked")
	public static <T> T getBeanFromXml(InputStream is, Class<T> clz) {
		T t = null;
		try{
			JAXBContext context = JAXBContext.newInstance(SysUser.class);
			Unmarshaller unmarshal = context.createUnmarshaller();
			t = (T) unmarshal.unmarshal(is);
		} catch (JAXBException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return t;
	}
    /**
	 * @author zml2015
	 * @Email zhengmingliang911@gmail.com
	 * @Time 2017年4月9日 下午6:32:02
	 * @Description <p> 将xml转换为指定对象 </P>
	 * @param xml  要解析的xml数据
	 * @param rootElemntName  要解析的内容的根标签名称
	 * @param clz 要转换成的实体类,
	 * @param attrs key值为要解析的xml标签中的属性值, value 值为属性值类型 
	 * @return
	 */
	public <T> List<T> parseXml2Bean(String xml,String rootElemntName,Class<T> clz,Map<String, Class> attrs){
		
		XMlHandler<T> handler = null;
		try(ByteArrayInputStream stream = new ByteArrayInputStream(xml.getBytes());) {
			handler = new XMlHandler<>(rootElemntName, clz, attrs);
			SAXParserFactory factory = SAXParserFactory.newInstance();
			SAXParser parser = factory.newSAXParser();
			parser.parse(stream, handler);
			System.out.println(handler.getDataList());
		} catch (ParserConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SAXException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return handler.getDataList();
	}
}

7. 测试类:

@Test
	public void testXML2Model1() throws IOException {
		String xml = HttpUtils.getHttpData("http://wcf.open.cnblogs.com/news/hot/10");
		Map<String,Class> map = new HashMap<>();
		map.put("link_href", String.class);
		List<BKYAticle> list = parseXml2Bean(xml,"entry", BKYAticle.class,map);

	}

8. 效果图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LLCMqTvk-1612149272748)(http://oexaoy5pt.bkt.clouddn.com/17-4-9/493522-file_1491736635077_1814a.png)]
点次查看完整大图(一定要找到中间那条线,然后在上面放大查看哦)

文章首次发布于个人博客:吾勇士的博客,点击查看原文

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值