2012年12月6日星期四

Android開發 - Load XML出現神秘的"#text"

今天幫公司寫Android的程式,其中一部份是類似[分店網絡]的頁面。
內含Google Map元件,載入XML,Databinding顯示Markers。

原來寫普通的Android App一點也不難。網上資源十分多,自己亦寫得很開心,因為自己第一個Android App就快可以誕生了。

但寫到一步,出現很奇怪的錯誤。
就是載入XML文件時,出現NullPointerException
這類似.NET中的NullReferenceException,簡單的說,就是找不到相應的類別物件。
在程式人員生涯中,Throw這個Exception已見慣見熟了吧?




XML結構如下 :
<?xml version="1.0" encoding="utf8"?>
<table name="shopnetwork">
 <row>
  <Code>1001</Code>
  <Shop>HongKong</Shop>
  <GeoPoint>22.3852860,113.9664120</GeoPoint>
 </row>
 <row>
  <Code>1002</Code>
  <Shop>Taiwan</Shop>
  <GeoPoint>22.336950,114.1578720</GeoPoint>
 </row>
 <row>
  <Code>1003</Code>
  <Shop>Japan</Shop>
  <GeoPoint>22.291660,114.2063270</GeoPoint>
 </row>
</table>


經簡化後的程式碼如下 :
若轉回C#的演繹方法就是讀XML檔案至StreamReader,再轉換成XmlDocument,再讀取Node List,一個很普遍的做法。
private void initGoogleMap() {
  AssetManager assetManager = this.getAssets();
  InputStream inputStream = null;
  String ID="", Shop = "";
  
  try {
   // Load XML for parsing.
   inputStream = assetManager.open("shops.xml");
   String xmlRecords = helper.readTextFile(inputStream);
 
   InputSource is = new InputSource();
   is.setCharacterStream(new StringReader(xmlRecords));
   xmlRecords = null;

   DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
   DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
   Document doc = docBuilder.parse(is);
   is = null;

   doc.getDocumentElement().normalize();

   NodeList listOfShops = doc.getElementsByTagName("row");
   
   int ListOfShopsLength = listOfShops.getLength();
  
   mIDs = new String[ListOfShopsLength];
   mShops= new String[ListOfShopsLength];
   
   // Loop the XML
   for (int x = 0; x < listOfShops.getLength(); x++) { 
    Node ShopsNode = listOfShops.item(x);
    NodeList ShopInfo = ShopsNode.getChildNodes();
    for (int y = 0; y < ShopInfo.getLength(); y++) {
     Node info = ShopInfo.item(y);
     //Log.e("Print Node Name (Tag Name)", info.getNodeName() );
      String NodeName = info.getNodeName().trim(); //<--Error!!!
      String NodeValue = info.getFirstChild().getNodeValue().trim();

      //Skip the use of GeoPoint.
      if (NodeName.equals("ID"))
       ID = NodeValue;
      else if (NodeName.equals("Shop"))
       Shop = NodeValue;

       mIDs[x] = ID;
       mShops[x]= Shop;
    } //End forloop (1)
   
   } //End forloop (2)
   // End Loop XML
  } catch (Exception e) {
   Log.e("Catch Exception", e.getMessage());
  }
 }

程式去到36行就會Error。 找了原因很久,因為就這樣看,XML文件是沒有任何問題的,程式碼又找不出那裡出現問題,或者自己對Eclipse還是摸不熟,Debug方面還是不夠Visual Studio來得易。 最後唯有用最原始的方法就是Print出數據看一看,uncomment Line35後,果然有所發現。 在LogCat的Windows出現 :
為什麼在XML中,<row>與<row>之間會有"#text"的出現呢? 是因為隔行的關係? 最後在網上找到答案,"#text"的出現就是因為換行/空白或Tab字元。 這在.NET Framework的世界是不會出現的,或者是.NET的XML Class已經幫我們代勞,但肯定就是XML規格來說,XML Node是可以換行的。
有很多對此的解釋和解決方法,可以參考:
Why are there #text nodes in my xml file?
Handle Empty #text in XML DOM
How to remove #text from my Node parsing in Java dom xml parsing

我自己就用最簡單的方法,就是用 if 做一個雙重檢測。
在上面的Line 35中,加入
if (info.getNodeName().toLowerCase() != "#text" && info.hasChildNodes()) {
//...
}
完成後的程式碼就是這樣 : (見Line 37 和 49)
private void initGoogleMap() {
  AssetManager assetManager = this.getAssets();
  InputStream inputStream = null;
  String ID="", Shop = "";
  
  try {
   // Load XML for parsing.
   inputStream = assetManager.open("shops.xml");
   String xmlRecords = helper.readTextFile(inputStream);
 
   InputSource is = new InputSource();
   is.setCharacterStream(new StringReader(xmlRecords));
   xmlRecords = null;

   DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
   DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
   Document doc = docBuilder.parse(is);
   is = null;

   doc.getDocumentElement().normalize();

   NodeList listOfShops = doc.getElementsByTagName("row");
   
   int ListOfShopsLength = listOfShops.getLength();
  
   mIDs = new String[ListOfShopsLength];
   mShops= new String[ListOfShopsLength];
   
   // Loop the XML
   for (int x = 0; x < listOfShops.getLength(); x++) { 
    Node ShopsNode = listOfShops.item(x);
    NodeList ShopInfo = ShopsNode.getChildNodes();
    for (int y = 0; y < ShopInfo.getLength(); y++) {
     Node info = ShopInfo.item(y);
     //Log.e("Print Node Name (Tag Name)", info.getNodeName() );

     if (info.getNodeName().toLowerCase() != "#text" && info.hasChildNodes()) {
      String NodeName = info.getNodeName().trim(); //<--Error!!!
      String NodeValue = info.getFirstChild().getNodeValue().trim();

      //Skip the use of GeoPoint.
      if (NodeName.equals("ID"))
       ID = NodeValue;
      else if (NodeName.equals("Shop"))
       Shop = NodeValue;

       mIDs[x] = ID;
       mShops[x]= Shop;
      }//End If

    } //End forloop (1)
   
   } //End forloop (2)
   // End Loop XML
  } catch (Exception e) {
   Log.e("Catch Exception", e.getMessage());
  }
 }

沒有留言:

發佈留言