【Android網絡開發の2】XML之SAX方式 解析和生成XML文件

上一章寫了如何使用DOM來解析和生成xml

接下來這章將講解SAX方式解析和生成xml

下面我們看下DOM和SAX的優缺點分析

  1. DOM(文件對象模型)解析:解析器讀入整個文檔,然後構建一個駐留內存的樹結構,然後代碼就可以根據DOM接口來操作這個樹結構了。  
  2.   
  3.   優點:整個文檔讀入內存,方便操作:支持修改、刪除和重現排列等多種功能。  
  4.   
  5.   缺點:將整個文檔讀入內存中,保留了過多的不需要的節點,浪費內存和空間。  
  6.   
  7.   使用場合:一旦讀入文檔,還需要多次對文檔進行操作,並且在硬件資源充足的情況下(內存,CPU)。  
  8.   
  9. 爲了解決DOM解析存在的問題,就出現了SAX解析。其特點爲:  
  10.   
  11.   優點:不用實現調入整個文檔,佔用資源少。尤其在嵌入式環境中,如android,極力推薦使用SAX解析。  
  12.   
  13.   缺點:不像DOM解析一樣將文檔長期駐留在內存中,數據不是持久的。如果事件過後沒有保存數據,數據就會丟失。  
  14.   
  15.   使用場合:機器有性能限制。  

一、SAX解析XML

下面是SAX實現實體解析的步驟

  1. //下面使用XMLReader 來解析 
  2. (一)第一步:新建一個工廠類SAXParserFactory,代碼如下: 
  3. SAXParserFactory factory = SAXParserFactory.newInstance(); 
  4. (二)第二步:讓工廠類產生一個SAX的解析類SAXParser,代碼如下: 
  5. SAXParser parser = factory.newSAXParser(); 
  6. (三)第三步:從SAXPsrser中得到一個XMLReader實例,代碼如下: 
  7. XMLReader reader = parser.getXMLReader(); 
  8. (四)第四步:把自己寫的handler註冊到XMLReader中,一般最重要的就是ContentHandler,代碼如下: 
  9. reader.setContentHandler(this); 
  10. (五)第五步:將一個xml文檔或者資源變成一個java可以處理的InputStream流後,解析正式開始,代碼如下: 
  11. reader.parse(new InputSource(is)); 
  12.  
  13.  
  14. //下面使用SAXParser來解析 
  15. (一)第一步:新建一個工廠類SAXParserFactory,代碼如下: 
  16. SAXParserFactory factory = SAXParserFactory.newInstance(); 
  17. (二)第二步:讓工廠類產生一個SAX的解析類SAXParser,代碼如下: 
  18. SAXParser parser = factory.newSAXParser(); 
  19. (三)第三步:將一個xml文檔或者資源變成一個java可以處理的InputStream流後,解析正式開始,代碼如下: 
  20. parser.parse(is,this); 

估計大家都看到了ContentHandler ,下面具體的講下

解析開始之前,需要向XMLReader/SAXParser 註冊一個ContentHandler,也就是相當於一個事件監聽器,在ContentHandler中定義了很多方法

  1. //設置一個可以定位文檔內容事件發生位置的定位器對象 
  2. public void setDocumentLocator(Locator locator) 
  3.  
  4. //用於處理文檔解析開始事件 
  5. public void startDocument()throws SAXException 
  6.  
  7. //處理元素開始事件,從參數中可以獲得元素所在名稱空間的uri,元素名稱,屬性類表等信息 
  8. public void startElement(String namespacesURI , String localName , String qName , Attributes atts) throws SAXException 
  9.  
  10. //處理元素結束事件,從參數中可以獲得元素所在名稱空間的uri,元素名稱等信息 
  11. public void endElement(String namespacesURI , String localName , String qName) throws SAXException 
  12.  
  13. //處理元素的字符內容,從參數中可以獲得內容 
  14. public void characters(char[] ch , int start , int length)  throws SAXException 

順便介紹下XMLReader中的方法。

  1. //註冊處理XML文檔解析事件ContentHandler 
  2. public void setContentHandler(ContentHandler handler) 
  3.  
  4. //開始解析一個XML文檔 
  5. public void parse(InputSorce input) throws SAXException 

下面是流程圖

大概的講的差不多了  接下來開始講解解析的步驟

我們還是用上一章的代碼

首先 我們創建一個Person類  用來存儲用戶的信息

  1. package com.example.demo; 
  2.  
  3. import java.io.Serializable; 
  4.  
  5. public class Person implements Serializable { 
  6.  
  7.     /** 
  8.      *  
  9.      */ 
  10.     private static final long serialVersionUID = 1L; 
  11.     private String _id; 
  12.     private String _name; 
  13.     private String _age; 
  14.  
  15.     public String get_id() { 
  16.         return _id; 
  17.     } 
  18.  
  19.     public void set_id(String _id) { 
  20.         this._id = _id; 
  21.     } 
  22.  
  23.     public String get_name() { 
  24.         return _name; 
  25.     } 
  26.  
  27.     public void set_name(String _name) { 
  28.         this._name = _name; 
  29.     } 
  30.  
  31.     public String get_age() { 
  32.         return _age; 
  33.     } 
  34.  
  35.     public void set_age(String _age) { 
  36.         this._age = _age; 
  37.     } 

接下來  我們要實現一個ContentHandler 用來解析XML

實現一個ContentHandler 一般需要下面幾個步驟

  1. 1、聲明一個類,繼承DefaultHandler。DefaultHandler是一個基類,這個類裏面簡單實現了一個ContentHandler。我們只需要重寫裏面的方法即可。 
  2. 2、重寫 startDocument() 和 endDocument(),一般將正式解析之前的初始化放到startDocument()裏面,收尾的工作放到endDocument()裏面。 
  3. 3、重寫startElement(),XML解析器遇到XML裏面的tag時就會調用這個函數。經常在這個函數內是通過對localName的值進行判斷而操作一些數據。 
  4. 4、重寫characters()方法,這是一個回調方法。解析器執行完startElement()後,解析節點的內容後就會執行這個方法,並且參數ch[]就是節點的內容。
  5. 5、重寫endElement()方法,這個方法與startElement()相對應,解析完一個tag節點後,執行這個方法,解析一個tag後,調用這個處理還原和清除相關信息 

首先   新建一個類 繼承DefaultHandler 並重寫以下幾個方法

  1. public class SAX_parserXML extends DefaultHandler { 
  2.  
  3.     /** 
  4.      * 當開始解析xml文件的聲明的時候就會觸發這個事件, 可以做一些初始化的工作 
  5.      * */ 
  6.     @Override 
  7.     public void startDocument() throws SAXException { 
  8.         // TODO Auto-generated method stub 
  9.         super.startDocument(); 
  10.          
  11.     } 
  12.  
  13.     /** 
  14.      * 當開始解析元素的開始標籤的時候,就會觸發這個事件 
  15.      * */ 
  16.     @Override 
  17.     public void startElement(String uri, String localName, String qName, 
  18.             Attributes attributes) throws SAXException { 
  19.         // TODO Auto-generated method stub 
  20.         super.startElement(uri, localName, qName, attributes); 
  21.     } 
  22.  
  23.     /** 
  24.      * 當讀到文本元素的時候要觸發這個事件. 
  25.      * */ 
  26.     @Override 
  27.     public void characters(char[] ch, int start, int length) 
  28.             throws SAXException { 
  29.         // TODO Auto-generated method stub 
  30.         super.characters(ch, start, length); 
  31.     } 
  32.  
  33.     /** 
  34.      * 當讀到結束標籤的時候 就會觸發這個事件 
  35.      * */ 
  36.     @Override 
  37.     public void endElement(String uri, String localName, String qName) 
  38.             throws SAXException { 
  39.         // TODO Auto-generated method stub 
  40.         super.endElement(uri, localName, qName); 
  41.     } 
  42.  

首先  我們創建一個list  用來保存解析出來的person數據

  1. List<Person> persons; 

但是?在哪裏初始化呢?我們可以在startDocument()裏面初始化,因爲當開始解析xml文件的聲明的時候就會觸發這個事件所以放在這裏比較合適

  1. /** 
  2.  * 當開始解析xml文件的聲明的時候就會觸發這個事件, 可以做一些初始化的工作 
  3.  * */ 
  4. @Override 
  5. public void startDocument() throws SAXException { 
  6.     // TODO Auto-generated method stub 
  7.     super.startDocument(); 
  8.     // 初始化list 
  9.     persons = new ArrayList<Person>(); 

接下來  就要開始解析了  

  1. /** 
  2.      * 當開始解析元素的開始標籤的時候,就會觸發這個事件 
  3.      * */ 
  4.     @Override 
  5.     public void startElement(String uri, String localName, String qName, 
  6.             Attributes attributes) throws SAXException { 
  7.         // TODO Auto-generated method stub 
  8.         super.startElement(uri, localName, qName, attributes); 
  9.  
  10.         // 如果讀到是person標籤 開始存儲 
  11.         if (localName.equals("person")) { 
  12.             person = new Person(); 
  13.             person.set_id(attributes.getValue("id")); 
  14.         } 
  15.         curNode = localName; 
  16.     } 

 上面的代碼中  localName表示當前解析到的元素名

  1. //步驟 
  2. //1.判斷是否是person元素 
  3. //2.創建新的Person對象 
  4. //3.獲取id 添加到Person對象中 

curNode 用來保存當前的元素名 在characters中會使用到

  1. /** 
  2.      * 當讀到文本元素的時候要觸發這個事件. 
  3.      * */ 
  4.     @Override 
  5.     public void characters(char[] ch, int start, int length) 
  6.             throws SAXException { 
  7.         // TODO Auto-generated method stub 
  8.         super.characters(ch, start, length); 
  9.  
  10.         if (person != null) { 
  11.             //取出目前元素對應的值 
  12.             String txt = new String(ch, start, length); 
  13.             //判斷元素是否是name 
  14.             if (curNode.equals("name")) { 
  15.                 //將取出的值添加到person對象 
  16.                 person.set_name(txt); 
  17.             } else if (curNode.equals("age")) { 
  18.                 person.set_age(txt); 
  19.             } 
  20.         } 
  21.     } 

接下來是介紹標籤結束的時候需要做的事情

  1. /** 
  2.  * 當讀到結束標籤的時候 就會觸發這個事件 
  3.  * */ 
  4. @Override 
  5. public void endElement(String uri, String localName, String qName) 
  6.         throws SAXException { 
  7.     // TODO Auto-generated method stub 
  8.     super.endElement(uri, localName, qName); 
  9.  
  10.     // 如果是 並且person不爲空,添加到list 
  11.     if (localName.equals("person") && person != null) { 
  12.         persons.add(person); 
  13.         person = null
  14.     } 
  15.  
  16.     curNode = ""

解析的事情結束了  大概流程就是

  1. 1.一個元素開始時    會調用startElement方法 
  2. 2.接下來會調用到characters方法,可以用來獲取元素的值 
  3. 3.一個元素結束時    會調用到endElement方法 

解析結束之後  我們需要寫一個方法  用來獲取解析後保存的list

  1. public List<Person> ReadXML(InputStream is) { 
  2.  
  3.         SAXParserFactory factory = SAXParserFactory.newInstance(); 
  4.         try { 
  5.             SAXParser parser = factory.newSAXParser(); 
  6.  
  7.             // 第一種方法 
  8.             // parser.parse(is, this); 
  9.  
  10.             // 第二種方法 
  11.             XMLReader reader = parser.getXMLReader(); 
  12.             reader.setContentHandler(this); 
  13.             reader.parse(new InputSource(is)); 
  14.  
  15.         } catch (Exception e) { 
  16.             // TODO: handle exception 
  17.             e.printStackTrace(); 
  18.         } 
  19.  
  20.         return persons; 
  21.     } 

上面的代碼就不解釋了   只要將inputStream對象傳入  就可以解析出內容

看完了代碼,我來給出完整的代碼

  1. package com.example.demo.Utils; 
  2.  
  3. import java.io.InputStream; 
  4. import java.util.ArrayList; 
  5. import java.util.List; 
  6.  
  7. import javax.xml.parsers.SAXParser; 
  8. import javax.xml.parsers.SAXParserFactory; 
  9.  
  10. import org.xml.sax.Attributes; 
  11. import org.xml.sax.InputSource; 
  12. import org.xml.sax.SAXException; 
  13. import org.xml.sax.XMLReader; 
  14. import org.xml.sax.helpers.DefaultHandler; 
  15.  
  16. import com.example.demo.Person; 
  17.  
  18. public class SAX_parserXML extends DefaultHandler { 
  19.  
  20.     List<Person> persons; 
  21.     Person person; 
  22.     // 當前節點 
  23.     String curNode; 
  24.  
  25.     public List<Person> ReadXML(InputStream is) { 
  26.  
  27.         SAXParserFactory factory = SAXParserFactory.newInstance(); 
  28.         try { 
  29.             SAXParser parser = factory.newSAXParser(); 
  30.  
  31.             // 第一種方法 
  32.             // parser.parse(is, this); 
  33.  
  34.             // 第二種方法 
  35.             XMLReader reader = parser.getXMLReader(); 
  36.             reader.setContentHandler(this); 
  37.             reader.parse(new InputSource(is)); 
  38.  
  39.         } catch (Exception e) { 
  40.             // TODO: handle exception 
  41.             e.printStackTrace(); 
  42.         } 
  43.  
  44.         return persons; 
  45.     } 
  46.  
  47.     /** 
  48.      * 當開始解析xml文件的聲明的時候就會觸發這個事件, 可以做一些初始化的工作 
  49.      * */ 
  50.     @Override 
  51.     public void startDocument() throws SAXException { 
  52.         // TODO Auto-generated method stub 
  53.         super.startDocument(); 
  54.         // 初始化list 
  55.         persons = new ArrayList<Person>(); 
  56.     } 
  57.  
  58.     /** 
  59.      * 當開始解析元素的開始標籤的時候,就會觸發這個事件 
  60.      * */ 
  61.     @Override 
  62.     public void startElement(String uri, String localName, String qName, 
  63.             Attributes attributes) throws SAXException { 
  64.         // TODO Auto-generated method stub 
  65.         super.startElement(uri, localName, qName, attributes); 
  66.  
  67.         // 如果讀到是person標籤 開始存儲 
  68.         if (localName.equals("person")) { 
  69.             person = new Person(); 
  70.             person.set_id(attributes.getValue("id")); 
  71.         } 
  72.         curNode = localName; 
  73.     } 
  74.  
  75.     /** 
  76.      * 當讀到文本元素的時候要觸發這個事件. 
  77.      * */ 
  78.     @Override 
  79.     public void characters(char[] ch, int start, int length) 
  80.             throws SAXException { 
  81.         // TODO Auto-generated method stub 
  82.         super.characters(ch, start, length); 
  83.  
  84.         if (person != null) { 
  85.             // 取出目前元素對應的值 
  86.             String txt = new String(ch, start, length); 
  87.             // 判斷元素是否是name 
  88.             if (curNode.equals("name")) { 
  89.                 // 將取出的值添加到person對象 
  90.                 person.set_name(txt); 
  91.             } else if (curNode.equals("age")) { 
  92.                 person.set_age(txt); 
  93.             } 
  94.         } 
  95.     } 
  96.  
  97.     /** 
  98.      * 當讀到結束標籤的時候 就會觸發這個事件 
  99.      * */ 
  100.     @Override 
  101.     public void endElement(String uri, String localName, String qName) 
  102.             throws SAXException { 
  103.         // TODO Auto-generated method stub 
  104.         super.endElement(uri, localName, qName); 
  105.  
  106.         // 如果是person結尾 並且person不爲空,添加到list 
  107.         if (localName.equals("person") && person != null) { 
  108.             persons.add(person); 
  109.             person = null
  110.         } 
  111.  
  112.         curNode = ""
  113.     } 
  114.  

 寫個方法調用下這個類

  1. List<Person> persons = new SAX_parserXML().ReadXML(is); 
  2.             StringBuffer buffer = new StringBuffer(); 
  3.             for (int i = 0; i < persons.size(); i++) { 
  4.                 Person person =persons.get(i); 
  5.                 buffer.append("id:" + person.get_id() + "   "); 
  6.                 buffer.append("name:" + person.get_name() + "   "); 
  7.                 buffer.append("age:" + person.get_age() + "\n"); 
  8.             } 
  9.             Toast.makeText(activity, buffer, Toast.LENGTH_LONG).show(); 

 如果你看到下面的界面  說明解析成功了~

 解析的問題  就講到這裏  如果有缺少的  有問題的   可以留言  會在博客中補充


二、SAX生成XML

Sax方式創建XML,應用了標準xml構造器 javax.xml.transform.sax.TransformerHandler 事件來創建 XML 文檔

首先,SAXTransformerFactory.newInstance() 創建一個工廠實例 factory

接着,factory.newTransformerHandler() 獲取 TransformerHandler 的 handler 對象

然後,通過 handler 事件創建handler.getTransformer()、 handler.setResult(result),以及 startDocument()、startElement、characters、endElement、endDocument()等

寫代碼之前   我們來講下幾個會用到的類

SAXTransformerFactory

此類擴展了 TransformerFactory 以提供特定於 SAX 的工廠方法。它提供兩種類型的 ContentHandler,一種用於創建 Transformer,另一種用於創建 Templates 對象。

如果應用程序希望設置轉換期間所使用的 XMLReader 的 ErrorHandler 或 EntityResolver,那麼它應使用 URIResolver 來返回提供了(通過 getXMLReader)對 XMLReader 引用的 SAXSource。

newTemplatesHandler()
獲取能夠將 SAX ContentHandler 事件處理爲 Templates 對象的 TemplatesHandler 對象。
newTransformerHandler()
獲取能夠將 SAX ContentHandler 事件處理爲 Result 的 TransformerHandler 對象。
newTransformerHandler(Source src)
基於參數所指定的轉換指令,獲取能夠將 SAX ContentHandler 事件處理爲 Result 的 TransformerHandler 對象。
newTransformerHandler(Templates templates) 基於 Templates 參數,獲取能夠將 SAX ContentHandler 事件處理爲 Result 的 TransformerHandler 對象。
newXMLFilter(Source src) 
創建使用給定 Source 作爲轉換指令的 XMLFilter
newXMLFilter(Templates templates)  
 基於 Templates 參數,創建 XMLFilter

 

TransformerHandler

偵聽 SAX ContentHandler 解析事件,並將它們轉換爲 Result 的 TransformerHandler

getTransformer() 獲取與此處理程序關聯的 Transformer,用於設置參數和輸出屬性。
setResult(Result result)  設置與用於轉換的此 TransformerHandler 關聯的 Result

 

 首先  我們來創建一個工程實例

  1. SAXTransformerFactory factory = (SAXTransformerFactory) SAXTransformerFactory 
  2.                     .newInstance(); 

接下來 獲取handler對象

  1. TransformerHandler handler = factory.newTransformerHandler(); 

 通過handler.getTransformer()獲取Transformer對象 ,然後設置xml的屬性

  1. // 獲取與此處理程序關聯的 Transformer,用於設置參數和輸出屬性。 
  2. Transformer info = handler.getTransformer();   
  3. // 是否自動添加額外的空白   
  4. info.setOutputProperty(OutputKeys.INDENT, "yes");   
  5. // 設置字符編碼   
  6. info.setOutputProperty(OutputKeys.ENCODING, "utf-8");   
  7.    
  8. info.setOutputProperty(OutputKeys.VERSION, "1.0");   

創建一個StreamResult對象用來保存創建的xml 

  1. StringWriter stringWriter = new StringWriter();  
  2. // 創建一個StreamResult對象用來保存創建的xml  
  3. StreamResult result = new StreamResult(stringWriter);  
  4. handler.setResult(result);  

下面開始處理生成xml,先創建測試數據

  1. private List<Person> getTestValues() { 
  2.         List<Person> persons = new ArrayList<Person>(); 
  3.         Person person = new Person(); 
  4.         person.set_id("23"); 
  5.         person.set_name("李磊"); 
  6.         person.set_age("30"); 
  7.         persons.add(person); 
  8.  
  9.         person = new Person(); 
  10.         person.set_id("20"); 
  11.         person.set_name("韓梅梅"); 
  12.         person.set_age("25"); 
  13.         persons.add(person); 
  14.  
  15.         return persons; 
  16.     } 

來看下最後生成的xml是什麼樣子的?

  1. <?xml version="1.0" encoding="UTF-8"?> 
  2. <persons> 
  3.     <person id="23"> 
  4.         <name>李磊</name> 
  5.         <age>30</age> 
  6.     </person> 
  7.     <person id="20"> 
  8.         <name>韓梅梅</name> 
  9.         <age>25</age> 
  10.     </person> 
  11. </persons> 

調用startDocument()表示開始 ,調用endDocument()表示結束,所以 寫代碼的時候直接把兩個都寫上,以防萬一最後忘記

  1. // 開始xml    
  2. handler.startDocument();    
  3. /*代碼寫在start和end之間*/  
  4. // 結束xml    
  5. handler.endDocument();   

接下來開始寫xml 第一步 創建根節點persons,同樣的,接下來的代碼卸載start和end之間

  1. AttributesImpl impl = new AttributesImpl(); 
  2. impl.clear(); 
  3. handler.startElement("""""persons", impl);  
  4. //創建要對應結束  所以寫完start  馬上補充end  
  5. handler.endElement("""""persons");  

 創建完根節點之後  開始創建person元素 person中含有一個id屬性

  1. impl.clear(); 
  2. impl.addAttribute("""""id""", person.get_id()); 
  3. handler.startElement("""""person", impl); 
  4. /*在這裏創建name和age元素*/ 
  5. handler.endElement("""""person"); 

下面創建name和age元素

  1. impl.clear(); 
  2. handler.startElement("""""name", impl); 
  3. String txt = person.get_name(); 
  4. handler.characters(txt.toCharArray(), 0, txt.length()); 
  5. handler.endElement("""""name"); 
  6.  
  7. impl.clear(); 
  8. handler.startElement("""""age", impl); 
  9. txt = person.get_age(); 
  10. handler.characters(txt.toCharArray(), 0, txt.length()); 
  11. handler.endElement("""""age"); 

看下完整代碼

  1. public String createXML() { 
  2.     // TODO Auto-generated method stub 
  3.  
  4.     StringWriter stringWriter = new StringWriter(); 
  5.     // 創建測試數據 
  6.     List<Person> persons = getTestValues(); 
  7.  
  8.     try { 
  9.         // 創建工廠 
  10.         SAXTransformerFactory factory = (SAXTransformerFactory) SAXTransformerFactory 
  11.                 .newInstance(); 
  12.  
  13.         TransformerHandler handler = factory.newTransformerHandler(); 
  14.  
  15.         Transformer info = handler.getTransformer(); 
  16.         // 是否自動添加額外的空白 
  17.         info.setOutputProperty(OutputKeys.INDENT, "yes"); 
  18.         // 設置字符編碼 
  19.         info.setOutputProperty(OutputKeys.ENCODING, "utf-8"); 
  20.  
  21.         info.setOutputProperty(OutputKeys.VERSION, "1.0"); 
  22.  
  23.         // 保存創建的xml 
  24.         StreamResult result = new StreamResult(stringWriter); 
  25.         handler.setResult(result); 
  26.  
  27.         // 開始xml 
  28.         handler.startDocument(); 
  29.  
  30.         AttributesImpl impl = new AttributesImpl(); 
  31.         impl.clear(); 
  32.         handler.startElement("""""persons", impl); 
  33.  
  34.         for (int i = 0; i < persons.size(); i++) { 
  35.             Person person = persons.get(i); 
  36.  
  37.             impl.clear(); 
  38.             impl.addAttribute("""""id""", person.get_id()); 
  39.             handler.startElement("""""person", impl); 
  40.  
  41.             impl.clear(); 
  42.             handler.startElement("""""name", impl); 
  43.             String txt = person.get_name(); 
  44.             handler.characters(txt.toCharArray(), 0, txt.length()); 
  45.             handler.endElement("""""name"); 
  46.  
  47.             impl.clear(); 
  48.             handler.startElement("""""age", impl); 
  49.             txt = person.get_age(); 
  50.             handler.characters(txt.toCharArray(), 0, txt.length()); 
  51.             handler.endElement("""""age"); 
  52.  
  53.             handler.endElement("""""person"); 
  54.  
  55.         } 
  56.  
  57.         handler.endElement("""""persons"); 
  58.  
  59.         // 結束xml 
  60.         handler.endDocument(); 
  61.  
  62.         return stringWriter.toString(); 
  63.     } catch (Exception e) { 
  64.         // TODO: handle exception 
  65.         e.printStackTrace(); 
  66.     } 
  67.  
  68.     return null

調用方法  查看返回的String是不是xml格式的  如果是的  表示調用成功了

我要講解了  也就結束了

個人語言水平有限  不懂的  可以留言  我修改

下面一章會講解pull解析和生成xml

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章