sax解析xml,驗證格式並支持自定義標籤

1、sax簡介

  SAX是事件驅動型的XML解析方式。順序讀取XML文件,生成事件,傳播到用戶定義的回調方法中來處理XML文件。java

  優勢:

    分段處理xml,而不是將整個xml一次加載進內存,內存佔用少,速度快。spring

  缺點:

    順序訪問,不能回退。編碼複雜,須要用戶把控數據結構。springboot

 

2、使用流程

  1.建立工廠網絡

SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();

   2.設置工廠屬性(可選)數據結構

saxParserFactory.setValidating(true); // 是否驗證xml,默認false
saxParserFactory.setNamespaceAware(true); // 是否展現命名空間 默認false

   3.生成解析器app

SAXParser parser = saxParserFactory.newSAXParser();

  4.設置解析器屬性(可選)maven

parser.setProperty("http://java.sun.com/xml/jaxp/properties/schemaLanguage", "http://www.w3.org/2001/XMLSchema"); // 驗證模式下必須

   5.獲取XMLReader(可選)ide

XMLReader reader = parser.getXMLReader();

   6.設置XMLReader屬性(可選)測試

reader.setContentHandler(new MyDefalutHandler()); // 內容處理器
reader.setEntityResolver(new MyEntityResolver()); // schama解析器
reader.setErrorHandler(new MyErrorHandler()); // 異常處理器

   7.解析xml文件ui

reader.parse(SaxDemo.class.getResource("/").getPath() + "/saxDemo.xml");
  或者 parser.parse(SaxDemo.class.getResource("/").getPath() + "/saxDemo.xml", new DefaultHandler());

   SAXParser和XMLReader均可以對xml進行解析,可是SAXParser將異常處理、內容處理和schema解析放到一個handler中進行重載,我的以爲職責劃分不是很清晰,建議使用XMLReader。

3、常規使用代碼示例

saxDemo.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xmlns:util="http://www.springframework.org/schema/util"
	   xmlns:aop="http://www.springframework.org/schema/cache"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
	   http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
	     http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd"

	   default-autowire="byName">

	<aop:annotation-driven></aop:annotation-driven>

	<util:map key-type="java.lang.String" value-type="java.lang.String">
		<entry key="key" value="value"></entry>
	</util:map>

	<bean id="commonMap" class="java.util.HashMap">
		<constructor-arg>
			<map>
				<entry key="key" value="value"></entry>
			</map>
		</constructor-arg>
	</bean>
</beans>

 MyDefalutHandler

public class MyDefalutHandler extends DefaultHandler{
    @Override
    public void startDocument() throws SAXException {
        System.out.println("startDocument()");
    }

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        StringBuilder builder = new StringBuilder();
        int length = attributes.getLength();
        if (length > 0) {
            for (int i = 0; i < length; i++) {
                builder.append(attributes.getLocalName(i).trim())
                        .append(":")
                        .append(attributes.getValue(i).trim())
                        .append(" ");
            }
        }

        System.out.println(String.format("startElement uri=%s localName=%s qname=%s attributes=%s", uri, localName, qName, builder));
    }

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        String content = new String(ch, start, length);
        if (StringUtils.isNotBlank(content)) {
            System.out.println("characters=" + content);
        }
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        System.out.println(String.format("startElement uri=%s localName=%s qname=%s", uri, localName, qName));
    }

    @Override
    public void endDocument() throws SAXException {
        System.out.println("endDocument()");
    }
}

 測試類

 1 public class SaxDemo {
 2     public static void main(String[] args) throws Exception {
 3         SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
 4 //        saxParserFactory.setValidating(true); // 是否驗證xml,默認false
 5 //        saxParserFactory.setNamespaceAware(true); // 是否展現命名空間 默認false
 6         SAXParser parser = saxParserFactory.newSAXParser();
 7 //        parser.setProperty("http://java.sun.com/xml/jaxp/properties/schemaLanguage", "http://www.w3.org/2001/XMLSchema");
 8         XMLReader reader = parser.getXMLReader();
 9         reader.setContentHandler(new MyDefalutHandler()); // 內容處理器
10 //        reader.setEntityResolver(new MyEntityResolver()); // schama解析器
11 //        reader.setErrorHandler(new MyErrorHandler()); // 異常處理器
12         reader.parse(SaxDemo.class.getResource("/").getPath() + "/saxDemo.xml");
13     }
14 }

 測試結果

startDocument()
startElement uri= localName= qname=beans attributes=xmlns:http://www.springframework.org/schema/beans xmlns:xsi:http://www.w3.org/2001/XMLSchema-instance xmlns:util:http://www.springframework.org/schema/util xmlns:aop:http://www.springframework.org/schema/cache xsi:schemaLocation:http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd     http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd       http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd default-autowire:byName 
startElement uri= localName= qname=aop:annotation-driven attributes=
startElement uri= localName= qname=aop:annotation-driven
startElement uri= localName= qname=util:map attributes=key-type:java.lang.String value-type:java.lang.String 
startElement uri= localName= qname=entry attributes=key:key value:value 
startElement uri= localName= qname=entry
startElement uri= localName= qname=util:map
startElement uri= localName= qname=bean attributes=id:commonMap class:java.util.HashMap 
startElement uri= localName= qname=constructor-arg attributes=
startElement uri= localName= qname=map attributes=
startElement uri= localName= qname=entry attributes=key:key value:value 
startElement uri= localName= qname=entry
startElement uri= localName= qname=map
startElement uri= localName= qname=constructor-arg
startElement uri= localName= qname=bean
startElement uri= localName= qname=beans
endDocument()

 結果中能夠看到uri和localName都是空,並且對aop:annotation-driven之類的解析不是很友好

打開測試類saxParserFactory.setNamespaceAware(true)的註釋,執行結果以下

 

startDocument()
startElement uri=http://www.springframework.org/schema/beans localName=beans qname=beans attributes=schemaLocation:http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd     http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd       http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd default-autowire:byName 
startElement uri=http://www.springframework.org/schema/cache localName=annotation-driven qname=aop:annotation-driven attributes=
startElement uri=http://www.springframework.org/schema/cache localName=annotation-driven qname=aop:annotation-driven
startElement uri=http://www.springframework.org/schema/util localName=map qname=util:map attributes=key-type:java.lang.String value-type:java.lang.String 
startElement uri=http://www.springframework.org/schema/beans localName=entry qname=entry attributes=key:key value:value 
startElement uri=http://www.springframework.org/schema/beans localName=entry qname=entry
startElement uri=http://www.springframework.org/schema/util localName=map qname=util:map
startElement uri=http://www.springframework.org/schema/beans localName=bean qname=bean attributes=id:commonMap class:java.util.HashMap 
startElement uri=http://www.springframework.org/schema/beans localName=constructor-arg qname=constructor-arg attributes=
startElement uri=http://www.springframework.org/schema/beans localName=map qname=map attributes=
startElement uri=http://www.springframework.org/schema/beans localName=entry qname=entry attributes=key:key value:value 
startElement uri=http://www.springframework.org/schema/beans localName=entry qname=entry
startElement uri=http://www.springframework.org/schema/beans localName=map qname=map
startElement uri=http://www.springframework.org/schema/beans localName=constructor-arg qname=constructor-arg
startElement uri=http://www.springframework.org/schema/beans localName=bean qname=bean
startElement uri=http://www.springframework.org/schema/beans localName=beans qname=beans
endDocument()

 4、驗證xml

若是咱們使用ide,得益於ide的驗證插件,編寫xml的時候能規避掉一部分輸入錯誤致使的格式異常。可是若是是經過xml進行rpc調用,咱們可能須要悲觀的設定獲得的xml並不必定是正確的,驗證xml是否知足既定格式就顯得十分重要了。

取消測試類的saxParserFactory.setValidating(true),開啓xml驗證功能。

警告: 已啓用驗證, 但未設置 org.xml.sax.ErrorHandler, 這可能不是預期結果。解析器將使用默認 ErrorHandler 來輸出前 0 個錯誤。請調用 'setErrorHandler' 方法以解決此問題。
Error: URI=file:///D:/idea/springboot2/target/classes//saxDemo.xml Line=2: 文檔無效: 找不到語法。
Error: URI=file:///D:/idea/springboot2/target/classes//saxDemo.xml Line=2: 文檔根元素 "beans" 必須匹配 DOCTYPE 根 "null"。

 根據提示,建立一個異常處理器

public class MyErrorHandler implements ErrorHandler {

    @Override
    public void warning(SAXParseException exception) throws SAXException {
        System.out.println("------------warning------------");
        throw exception;
    }

    @Override
    public void error(SAXParseException exception) throws SAXException {
        System.out.println("------------error------------");
        throw exception;
    }

    @Override
    public void fatalError(SAXParseException exception) throws SAXException {
        System.out.println("------------fatalError------------");
        throw exception;
    }
}

 取消測試類reader.setErrorHandler(new MyErrorHandler())的註釋

Exception in thread "main" org.xml.sax.SAXParseException; systemId: file:///D:/idea/springboot2/target/classes//saxDemo.xml; lineNumber: 2; columnNumber: 7; 文檔無效: 找不到語法。

 仍是異常!!!

出現這個問題的緣由是sax不知道遵循哪一個xml規範

 取消測試類parser.setProperty("http://java.sun.com/xml/jaxp/properties/schemaLanguage", "http://www.w3.org/2001/XMLSchema")的註釋,程序正常執行了。

 5、自定義標籤

maven工程下,resources目錄建立META-INF文件夾,生成2個文件:saxDemo.schemas user.xsd

saxDemo.schemas內容:

http\://www.ym.com/schema/user.xsd=META-INF/user.xsd

 user.xsd內容

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            xmlns="http://www.ym.com/schema/user" targetNamespace="http://www.ym.com/schema/user"
            elementFormDefault="qualified">
    <xsd:element name="user">
        <xsd:complexType>
            <xsd:attribute name="id" type="xsd:string" />
            <xsd:attribute name="userName" type="xsd:string" />
            <xsd:attribute name="email" type="xsd:string" />
        </xsd:complexType>
    </xsd:element>
</xsd:schema>

 修改samDemo.xml文件爲:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xmlns:user="http://www.ym.com/schema/user"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
	   http://www.ym.com/schema/user http://www.ym.com/schema/user.xsd"
	   default-autowire="byName">
	<user:user id="userTag" userName="userName" email="email"></user:user>
</beans>

 執行測試代碼

------------fatalError------------
Exception in thread "main" org.xml.sax.SAXParseException; systemId: http://www.ym.com/schema/user.xsd; lineNumber: 1; columnNumber: 50; 在 publicId 和 systemId 之間須要有空格。

 拋出異常

爲何spring的標籤沒啥問題,而咱們自定義的不行呢?這是由於spring的標籤能夠從網絡上獲取,可是咱們自定義的基本上都在咱們本地,並且不少時候咱們也不但願從網絡中獲取這些資源,更但願使用本地jar包中的。

EntityResolver的做用就是從本地加載標籤資源,驗證xml的正確性

建立MyEntityResolver從META-INF下讀取資源

(代碼來自spring源碼org.springframework.beans.factory.xml.PluggableSchemaResolver,刪除了全部日誌的內容)

public class MyEntityResolver implements EntityResolver {

    private volatile Map<String, String> schemaMappings;
    private final String schemaMappingsLocation = "META-INF/saxDemo.schemas";
    @Override
    public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
        if (systemId != null) {
            String resourceLocation = getSchemaMappings().get(systemId);
            if (resourceLocation != null) {
                Resource resource = new ClassPathResource(resourceLocation, Thread.currentThread().getContextClassLoader());
                try {
                    InputSource source = new InputSource(resource.getInputStream());
                    source.setPublicId(publicId);
                    source.setSystemId(systemId);return source;
                }
                catch (FileNotFoundException ex) {
                }
            }
        }
        return null;
    }

    private Map<String, String> getSchemaMappings() {
        Map<String, String> schemaMappings = this.schemaMappings;
        if (schemaMappings == null) {
            synchronized (this) {
                schemaMappings = this.schemaMappings;
                if (schemaMappings == null) {try {
                        Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, Thread.currentThread().getContextClassLoader());
                        Map<String, String> mappingsToUse = new ConcurrentHashMap<>(mappings.size());
                        CollectionUtils.mergePropertiesIntoMap(mappings, mappingsToUse);
                        schemaMappings = mappingsToUse;
                        this.schemaMappings = schemaMappings;
                    }
                    catch (IOException ex) {
                        throw new IllegalStateException("Unable to load schema mappings from location [" + this.schemaMappingsLocation + "]", ex);
                    }
                }
            }
        }
        return schemaMappings;
    }
}

如今打開測試代碼reader.setEntityResolver(new MyEntityResolver())的註釋

startDocument()
23:23:53.832 [main] DEBUG com.ym.xml.MyEntityResolver - Loading schema mappings from [META-INF/saxDemo.schemas]
23:23:53.839 [main] DEBUG com.ym.xml.MyEntityResolver - Loaded schema mappings: {http://www.ym.com/schema/user.xsd=META-INF/user.xsd}
startElement uri=http://www.springframework.org/schema/beans localName=beans qname=beans attributes=schemaLocation:http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.ym.com/schema/user http://www.ym.com/schema/user.xsd default-autowire:byName default-lazy-init:default default-merge:default 
23:23:54.614 [main] DEBUG com.ym.xml.MyEntityResolver - Found XML schema [http://www.ym.com/schema/user.xsd] in classpath: META-INF/user.xsd
startElement uri=http://www.ym.com/schema/user localName=user qname=user:user attributes=id:userTag userName:userName email:email 
startElement uri=http://www.ym.com/schema/user localName=user qname=user:user
startElement uri=http://www.springframework.org/schema/beans localName=beans qname=beans
endDocument()

 7、總結

  本文介紹了sax的基本概念、調用流程、經常使用的使用方式。

  saxParserFactory.setValidating(true) 開啓驗證功能

  setNamespaceAware(true) 生成詳細的事件記錄

  setProperty("http://java.sun.com/xml/jaxp/properties/schemaLanguage", "http://www.w3.org/2001/XMLSchema") 使用xsd驗證規則

  setEntityResolver(new MyEntityResolver())  使用自定義標籤

  setErrorHandler(new MyErrorHandler()) 自定義異常處理

相關文章
相關標籤/搜索