XML 與 XML Schema的使用教程


引言:我寫本文的宗旨在於給需要使用XML,而又對XML不是很熟悉的人們提供一種使用思路,而不沒有給出具體的使用方法,至於下文中提到的使用方法,還未嘗試過,都是從網上整理而來!

 

一、概述

什么是XML?

  xm是l可擴展標記語言縮寫,xml是互聯網數據傳輸的重要工具,它可以跨越互聯網任何的平台,不受編程語言和

操作系統的限制,可以說它是一個擁有互聯網最高級別通行證的數據攜帶者。xml是當前處理結構化文檔信息中相當

給力的技術,xml有助於在服務器之間穿梭結構化數據,這使得開發人員更加得心應手的控制數據的存儲和傳輸。

 

什么是DTO?

DTD的作用是定義XML的合法構建模塊,它使用一系列的合法元素來定義文檔結構。

 

什么是XML Schema?

XML Schema是對XML文檔結構的定義和描述,其主要的作用是用來約束XML文件,並驗證XML文件有效性。

由於XML中可以使用自己定義的元素,由於是自己定義的,當給別人使用的時候,別人怎么知道你的XML文件中寫是什么東西呢?

這就需要在XML Scheme文件中聲明你自己定義了些什么元素,這些元素都有些什么屬性。

 

Schema與DTO的區別:

(1) Schema本身也是XML文檔,DTD定義跟XML沒有什么關系,Schema在理解和實際應用有很多的好處。

(2) DTD文檔的結構是“平鋪型”的,如果定義復雜的XML文檔,很難把握各元素之間的嵌套關系;Schema文檔結構性強,各元素之間的嵌套關系非常直觀。

(3) DTD只能指定元素含有文本,不能定義元素文本的具體類型,如字符型、整型、日期型、自定義類型等。Schema在這方面比DTD強大。

(4) Schema支持元素節點順序的描述,DTD沒有提供無序情況的描述,要定義無序必需窮舉排列的所有情況。Schema可以利用xs:all來表示無序的情況。

(5) 對命名空間的支持。DTD無法利用XML的命名空間,Schema很好滿足命名空間。並且,Schema還提供了include和import兩種引用命名空間的方法。

 

SAX解析:

SAX,全稱Simple API for XML,是一種以事件驅動的XMl API,是XML解析的一種新的替代方法,解析XML常用的還有DOM解析,PULL解析(Android特有),SAX與DOM不同的是它邊掃描邊解析,自頂向下依次解析,由於邊掃描邊解析,所以它解析XML具有速度快,占用內存少的優點,對於Android等CPU資源寶貴的移動平台來說是一個巨大的優勢。 

Java JDK自帶的解析(SAXParserFactory SAXPaeser DefaultHandler)

特點: 一行一行的往下面執行解析的

 

二、使用步驟及案例

1. SAX解析步驟

 

解析步驟:
 1.創建一個SAXParserFactory對象
    SAXParserFactory factory=SAXParserFactory.newInstance();

 2.獲得解析器    
    SAXParser parser=factory.newSAXParser();

 3.調用解析方法解析xml,這里的第一個參數可以傳遞文件、流、字符串、需要注意第二個參數(new DefaultHander)
    File file=new File("girls.xml");
    parser.parse(file,new DefaultHandler());
    /**注解:--->這里的DefaultHandler表示
    DefaultHandler類是SAX2事件處理程序的默認基類。它繼承了EntityResolver、DTDHandler、
    ContentHandler和ErrorHandler這四個接口。包含這四個接口的所有方法,所以我們在編寫事件處理程序時,
    可以不用直接實現這四個接口,而繼承該類,然后重寫我們需要的方法,所以在這之前我們先定義一個用於實現解析
    方法如下:*/

 4.創建一個MyHandler類來繼承DefaultHandler並重寫方法
    //定一個名為MyHandler類用來繼承DefaultHandler
    (1)MyHandler extends DefaultHander
    (2)重寫方法,快速記住方法(2個開始,2個結束,1一個文字(charactor--里面的內容))
    (3)2個開始:StartDocment(文檔的開始)StartElement(元素的開始) 2個結束:endElement(元素的結束)
     endDocment(文檔的結束,標志着xml文件的結束) 1個文字內容:charactor(文字內容)

 5.創建一個集合把所解析的內容添加到集合
    //分析:目的我們只是需要把xml里面的文字內容添加到我們的集合而不需要其他元素,所以我們需要進行判斷得到
    //(接上)我們需要的內容(下面會賦一個圖幫助理解)

 6.接步驟三 輸出集合System.out.pritnln(list); 解析完成!

 

解析流程圖:

(1) 待解析的xml

<?xml version="1.0" encoding="utf-8" ?>
<girls>
    <girl id="1">
        <name>黃夢瑩</name>
        <age>32</age>
    </girl>
    <girl id="2">
        <name>劉亦菲</name>
        <age>33</age>
    </girl>
</girls>

(2) 用於封裝數據的實體

/**
 * 封裝實體
 *
 * @author zls
 * @date 2020/3/19
 */
@Data
public class Girl {
    private String id;
    private String name;
    private String age;
}

(3) 自定義解析xml的類

package sax;

import lombok.Data;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * 自定義xml文件解析的實現類
 * 原理:sax是一行一行的往下面執行解析的
 *
 * 參考:https://blog.csdn.net/lsh364797468/article/details/51325540
 * @author zls
 * @date 2020/3/19
 */
@Data
public class MyHandler extends DefaultHandler {
    //  准備一個用於添加xml數據的集合、調用Girl類、准一個用於用來保存開始的標簽的tag
    private List<Girl> girls;
    private Girl girl;
    private String tag; // 記錄xml中元素表中,因為是一行一行的解析的,所以需要記錄

    /**
     * 文檔的開始
     * @throws SAXException
     */
    @Override
    public void startDocument() throws SAXException {
        super.startDocument();
        //  因為這個方法只調用一次,所以在開始的時候就可以實例化集合
        girls = new ArrayList<>();
    }

    /**
     * 文檔結束
     * @throws SAXException
     */
    @Override
    public void endDocument() throws SAXException {
        super.endDocument();
    }

    /**
     * 元素的開始
     * @param uri
     * @param localName
     * @param qName  每次遍歷取得的標簽,每次只取一行
     * @param attributes
     * @throws SAXException
     */
    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        super.startElement(uri, localName, qName, attributes);
        //  這個方法,只有當開始一個元素的時候才會調用,
        // 通過分析,當外部開始元素為girl的時候,需要將girl實例化

        // 將tag賦值
        tag = qName;
        if ("girl".equals(qName)) {
            girl = new Girl();
        }
    }

    /**
     * 元素的結束
     * @param uri
     * @param localName
     * @param qName
     * @throws SAXException
     */
    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {

        super.endElement(uri, localName, qName);
        // 這句話,必須寫,因為,當sax解析完一個元素的時候,會自動認為換行符是一個字符,會繼續執行 character 方法 。如果不寫,就會造成沒有數據的現象。
        tag = "";

        // 這個方法,當到了元素結尾的時候,會調用,應該在這里,將對象添加到集合里面去。
        if ("girl".equals(qName)) {
            girls.add(girl);
        }
    }

    /**
     * 文字內容
     * @param ch
     * @param start
     * @param length
     * @throws SAXException
     */
    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        super.characters(ch, start, length);
        // 這里是內容,但是,無法直接判斷屬於哪一個元素。
        String string = new String(ch, start, length);
        //這兩種情況,表示 當前語句執行在 girls 標簽內。
        if ("name".equals(tag)) {//判斷當前內容,屬於哪一個元素。
            girl.setName(string);
        } else if ("age".equals(tag)) {
            girl.setAge(string);
        }

//        try {
//            setFiledValue(tag, string);
//        } catch (IllegalAccessException e) {
//            e.printStackTrace();
//        }
    }

    /**
     * 使用反射的方式設置字段的值
     *
     * (1) 字段很多的情況
     * 問題:
     * 如果xml中的標簽很多,即:類中的屬性很多,那么采用以上的方式,難道要寫十萬個if嗎?
     * 所以這里采用反射的方式,自動的給對象的屬性設置上值(學了那么多年的反射終於有了用武之地了)。
     *
     * (2) 類中的屬性和xml中的標簽有點不一樣的情況
     * 問題:我遇到的情況是第三方提供的xml里面所有標簽都是大寫字母,而我們項目字段采用的駝峰命名法,那么如果直接把xml中的標簽
     * 定義為屬性名,那多難看啊,幾個單詞連在一起都是大寫,除了開發的人員自己能看懂以外,以后維護的人員看到了肯定會問候你祖上十八代的。
     * 解決:為此,我采用了駝峰命名的方式給實體屬性命名,反射的時候可以轉換為大寫字母和xml中的標簽比較
     *
     * (3) 除了用代理的方式,還可以用json轉對象的方式
     * 我們可以手動的拼接成一個json字符串,然后再采用json轉對象的方式也是可以的。這種方式理論上也是可以的,有興趣可以自己實現以下。
     * @param tag
     * @param value
     */
    public void setFiledValue(String tag, String value) throws IllegalAccessException {
        if (girl == null) {
            return;
        }
        Field[] declaredFields = girl.getClass().getDeclaredFields();
        for (Field declaredField : declaredFields) {
            if (Objects.equals(declaredField.getName(), tag)) {
                // 修改字段訪問控制權限(字段是私有的,則需要設為可以訪問)
                if(!declaredField.isAccessible()){
                    declaredField.setAccessible(true);
                }
                // 字段類型
                String fieldType = declaredField.getType().getSimpleName();
                System.out.println("字段:"+ declaredField.getName()+", 字段類型為: " + fieldType);
                declaredField.set(girl, value);
            }
        }
    }
}
View Code

(4) 測試類

/**
 * @author zls
 * @date 2020/3/19
 */
public class SaxPareTest {
    // 獲取test下的resource路徑,在測試文件夾下測試時,非類文件必須放在resource及其子文件夾下,要不然編譯之后文件就沒了
    private static String path = SaxPareTest.class.getClass().getResource("/").getPath();

    public static void main(String[] args) {
        // 1.創建對象
        SAXParserFactory newInstance = SAXParserFactory.newInstance();
        try {
            // 2.獲取解析器
            SAXParser saxParser = newInstance.newSAXParser();
            // 3.調用方法開始解析xml
            // 這里的路徑到時候根據自己的需要去拼,只要能找到文件即可
            String absolutePath = path + "girls.xml";
            File file = new File(absolutePath);
            MyHandler dh = new MyHandler();
            saxParser.parse(file, dh);
            List<Girl> girls = dh.getGirls();
            // 4.輸出集合
            System.out.println(girls);
            // [Girl(id=null, name=黃夢瑩, age=32), Girl(id=null, name=劉亦菲, age=33)]
        } catch (ParserConfigurationException | SAXException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
View Code

 

2. 解析XML文件(XML的解析使用 - 消費者)

  在比較大的項目中,我們有時會用到服務這個概念,一些服務會以xml的形式返回結果,這個時候就要對XML進行解析,

但很多時候,我們對服務提供的XML結構不甚了解,就算了解了,如果服務被修改XML結構被改變,這個時候以前寫好

的解析XML的方法就會出現紊亂,如何解決這個問題呢?其實標准的服務在提供給用戶XML的時候會提供給用戶對應的

XML描述文件,這就是XSD文件,對此文件進行解析后再利用解析后的XSD文件對XML進行解析,這樣即使服務節點變了,

后台的代碼也能正確解析當前服務返回的XML文件。

 

使用方法,參考(案例1):

https://luo-yifan.iteye.com/blog/2036072

 

我們在使用XSD文件的時候,可以通過cmd命令的形式,將XSD文件生成java文件,生成方法參考:

使用jdk的xjc命令由schema文件生成相應的實體類

 

另外附上一篇寫的比較好的xml文件解析的基礎教程,參考:

 XML文件詳解以及解析

 

案例2:解析XML文件中的數據,然后將解析后的數據保存到數據庫

參考:

 java解析xml並把節點值存儲到數據庫中

 

2. 生成XML文件(生產者)

案例1:java制作xml文件

參考:java生成XML文件

 

案例2:根據xsd定義的xml格式封裝數據並生成xml文件

參考:https://zmft1984.iteye.com/blog/798384

背景:

  在項目組最近一次的版本開發中,有個需求是將我們項目數據庫中的基礎數據生成到xml文件中,並通知第三方獲取這個xml文件。

剛開始是想將數據按照xml的格式拼接成字符串,並逐行的寫到文件中,但是總覺得這樣做和java的面向對象格格不入。於是baidu了一

下,果然有比拼接字符串更好的辦法,具體實現步驟如下:
(1) 將要生成的xml文件的格式編寫到xsd文件中,如我們項目中用到的Channel.xsd;
(2) 借助於jdk的xjc命令,將xml中的節點轉換成java對象類以供數據封裝,具體實現是編寫一個bat文件,直接執行bat文件即可;
(3) 准備數據封裝,將從數據庫中查詢出來的數據按照xml格式從底層節點開始,逐層向上封裝;
(4) 使用jdk的JAXBContext將封裝好的數據輸出到xml文件。

 

案例3:根據xsd定義的xml格式封裝數據並生成xml文件

背景概述:

  由於很多廠家在開發同一套系統,而我們公司只是參與了系統中的一部分,因此別的公司要求我們公司

將系統中某幾個模塊功能的數據封裝成為一個xml文件(當系統中這幾個模塊點擊添加,然后是填寫各種數據,

最后保存表單提交時需要生產一個xml文件),然后上傳到ftp服務器,其他公司給我們提供了一個XSD文件,

里面描述了對生產XML文件的要求,同時也還提供了一個word文檔說明哪個元素里面放什么內容。

開發步驟:

  其實,這個需求就相當於案例2中的一樣,說白了就是要我們根據xsd定義的xml格式封裝數據,並生產xml文件。

但是感覺使用案例2中的方式太過去麻煩,於是就想有沒沒有什么簡單的方式呢?於是:

由於公司使用的是springboot框架,里面有一個東西叫thymeleaf,而我們可以自己定義模板,於是我們基於模板去生成

xml文件。

具體步驟:

(1) 更具提供的xsd文檔,自己制作一個html模板,並將自己制作的這個模板和springboot集成;

(2) 將自己系統中的業務數據傳遞給這個模板,然后通過thymeleaf中的表達式寫到這個html模板上(類似於我們平時渲染html頁面一樣);

(3) 將渲染完的這個模板后綴修改為.xml,然后通過ftp上傳到別的公司要求的目標地址上。

 

具體代碼的話后期在提供:

 代碼業務方法:

/**
 * 測試類
 */
public class Test{

    @Autowired
    private IUserService userService;

    @Test
    public void test(){        
        //這里對應的是user模板.html頁面
        ModelAndView view = new ModelAndView("user模板");
        view.addObject("username", "gaigai");
        view.addObject("age", 23);
        userService.upload(view,"user自動發布文件.xml");
    }
}
View Code

UserServiceImpl中的給springboot添加模板的方法:

springboot中模板引擎的使用,可以參考:thymeleaf模板引擎入門(當然,這個參考文檔沒有用springboot,

如果用springboot的話,集成起來應該更方便!)

public void upload(ModelAndView view, String filename) {

    FtpClient ftpClient = null;
    try {
        //Context用於保存模板中需要的一些變量。例如要把變量傳遞到模板中,
        //就可以先把變量放入IContext的實現類中,然后在模板中獲取該變量的值。
        Context context = new Context(Locale.CHINESE,view.getModel());
        
        //process方法用於解析模板並利用當前response對象的writer把模板輸出到瀏覽器。
        String template = templateEngine.process(view.getViewName(), context);
        
        //通過ftp服務器上傳到指定的目錄
        ByteArrayInputStream bais = new ByteArrayInputStream(template.getBytes("UTF-8"));
        ftpClient = ftpSource.borrowObject();
        ftpClient.putFile(filename,bais);
    } catch (Exception e) {
        //"獲取ftp連接異常!
    } finally {
        if(ftpClient != null){
            ftpSource.returnObject(ftpClient); //org.apache.commons.pool2 jar包
        }

    }
}
View Code

user模板.html 頁面為:

<?xml version="1.0" encoding="UTF-8" ?>
<user xmlns:th="http://www.thymeleaf.org">
      <username th:text="${username}">花千骨</username>
    <age th:text="${age}">32</age>   
</user>
View Code

我這里最終生成的 user自動發布文件.xml 和這里的html頁面內容一致,只不過是里面的數據被替換了。

 

參考:

SAX解析詳解全過程

XML使用教程  

XML Schema使用教程

Schema和DTD的區別

SAX解析、DOM解析XML、JDOM解析的區別

SAX解析與DOM解析的區別

SAX解析xml


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM