Unity xml反序列化为数据类

发表于2018-08-01
评论0 3.3k浏览
xml在unity中,经常用来保存本地数据,我们在处理xml文件的时候,通常是先创建一个xml对应的数据类,然后将xml解析的每一个element封装成对象,在保存的list或者diction集合中。解析xml的过程大部分都是相同的,但是不同的数据类,对应的字段,相差较大,所以之前在解析的时候,都是单独解析的。  

最近闲来无事,就讲xml解析的方法进行了加工,利用c#的反射技术和泛型的特点,创建一个xml解析工具,这样,我们只用传递xml文件以及生产文件的数据类,就可以轻松搞定繁琐的xml转换了。  

首先我们需要一个xml文件,手写xml文件太费劲了,我们可以先在Excel中创建数据,保存为CSV格式的文件,然后在写个工具,把csv文件转换为xml文件(虽然这样也有点麻烦,但是感觉比直接写xml要好一点)  

直接上代码  :
using UnityEngine;
using System.Xml;
using System.IO;
using UnityEditor;
//using System;
/// <summary>
/// 把给定列数的csv文件转换为xml文件.该类文件放在file/csv文件夹下
/// csv文件的格式:第一行为属性的名称,后面若干行为属性的值
/// 生成的xml文件,放在StreamingAssets/xml目录下
/// </summary>
public class CsvToXmlTools : Editor {
    [MenuItem("Assets/CsvToXml/Story")]
    static void CsvToXml_Story(){
         string path=AssetDatabase.GetAssetPath (Selection.activeObject);
        if(path.EndsWith(".csv"))
            loadFile (path);
    }
    /// <summary>
    /// 加载csv文件,并按行读取数据.
    /// </summary>
    /// <param name="filePath">文件路径.</param>
    /// <param name="columCount">列数.</param>
    static  void loadFile(string filePath){
        string path = filePath;
        string saveName = Path.GetFileNameWithoutExtension(path);
        string xmlPath = Application.streamingAssetsPath + "/xml/" +saveName+ ".xml";
        CreatXmlFile (xmlPath);//创建或者覆盖之前的xml文件
        //读取csv文件
        FileStream fs = new FileStream (path,FileMode.Open);
        using(StreamReader sr=new StreamReader(fs)){
            string str = sr.ReadLine ();
            string[] keys = str.Split (',');//保存属性名称
//          str = sr.ReadLine ();
//          addToXml (new string[]{"dataType"},new string[]{str},xmlPath);//保存数据格式
            int columCount = keys.Length ;
            while ((str=sr.ReadLine ())!=null) {
                string[] arr = str.Split (',');
                if (keys.Length ==  arr.Length) {
                    addToXml (keys,arr,xmlPath);//保存读取的数据到xml文件中
                } else
                    Debug.LogError ("data error"+str);
            }
        }
        fs.Close ();
        Debug.Log ("转换文件结束");
    }
    /// <summary>
    /// 为xml文件添加新的节点.
    /// </summary>
    /// <param name="keys">需要添加的数据的健.</param>
    /// <param name="arr">需要添加的数据值.</param>
    /// <param name="xmlPath">xml文件的位置.</param>
    static  void addToXml(string[] keys,string[] values,string xmlPath){
        XmlDocument xmldoc = new XmlDocument ();
        xmldoc.Load (xmlPath);
        XmlNode root= xmldoc.SelectSingleNode ("root");
        XmlElement record = xmldoc.CreateElement ("Record");
        for (int i = 0; i < keys.Length; i++) {
//          Debug.Log(keys[i]+"-------"+values[i]);
            record.SetAttribute (keys [i], values [i]);
        }
        root.AppendChild (record);
        xmldoc.Save (xmlPath);
    }
    /// <summary>
    /// 创建一个新的xml文件.
    /// </summary>
    /// <param name="path">xml文件的保存路径.</param>
    static  void CreatXmlFile(string path){
        XmlDocument doc = new XmlDocument ();
        XmlDeclaration dec= doc.CreateXmlDeclaration("1.0", "utf-8", null);
        doc.AppendChild (dec);
        XmlNode root= doc.CreateElement ("root");
        doc.AppendChild (root);
        doc.Save (path);
        Debug.Log ("save path=" + path);
    }
}
这是我们的编辑器工具,选择我们的csv文件,右键,选CsvToXml/Story,即可,生成的xml文件自动保存在StreamingAssets/xml/目录下,没有该目录的需要自己手动创建一下,代码里就懒得做处理了。

然后就是我们的xml解析类,我们把xml解析放在基类里面,子类只用继承调用即可。
每一个xml文件,会有一个单独的数据类和它对应,每一个字段对应一个属性。

首先是我们的xml文件  
<?xml version="1.0" encoding="utf-8"?>
<root>
  <Record id="1" chapter="1" index="1" content="你是故意与我争斗,以骗取启魂邪教的信任?" hero="aa" heroName="洛昭言" type="true" />
  <Record id="2" chapter="1" index="2" content="没错。不瞒兄弟,我们两人昨天刚到盈辉堡,这丫头就上了刚才那家伙的当,把钱全给了那个什么圣宗。没办法,我只好装傻跟那个教使套近乎,看能不能想法把钱弄回来" hero="bb" heroName="越今朝" type="false" />
  <Record id="3" chapter="1" index="3" content="我没上当,以前就见过穿这种衣服的骗子啊。可是你说过,要是别人都去做一件事,我最好也跟着做。昨天好多人都把钱给他了啊。" hero="aa" heroName="祈" type="true" />
  <Record id="4" chapter="1" index="4" content="我教过你明知是坑还往里跳吗?" hero="aa" heroName="越今朝" type="true" />
  <Record id="5" chapter="1" index="5" content="我教过你明知是坑还往里跳吗?" hero="bb" heroName="祈" type="false" />
  <Record id="6" chapter="2" index="1" content="我一定是上辈子欠你的…… " hero="aa" heroName="越今朝" type="true" />
  <Record id="7" chapter="2" index="2" content="加斯递交的年代" hero="bb" heroName="洛昭言" type="false" />
  <Record id="8" chapter="2" index="3" content=":那个……两位……" hero="aa" heroName="aa" type="true" />
  <Record id="9" chapter="2" index="4" content="呃……咳咳。我叫越今朝,这是越祈,兄弟怎么称呼?" hero="bb" heroName="越今朝" type="false" />
  <Record id="10" chapter="2" index="5" content="洛昭言" hero="aa" heroName="洛昭言" type="true" />
  <Record id="11" chapter="2" index="6" content="难道您就是这一带有名的昙华洛家的家主?" hero="bb" heroName="越今朝" type="false" />
</root>

然后使我们的数据基类  
public class ModelBase  {
    public int id;
}

数据类  
public  class StoryEntry:ModelBase{
        public string chapter;
        public string index;
        public string content;
        public string hero;
        public string heroName;
        public bool type;
//      public void show(){
//          string str = "";
//          //取得当前方法命名空间
//          str += "命名空间名:" + System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Namespace + "\n";
//          //取得当前方法类全名
//          str += "类名:" + System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName + "\n";
//          Debug.Log (str);
//      }
    }

xml解析的基类  
using System.Collections;
using System.Collections.Generic;
using System.Xml;
using System.Reflection;
public class ModelMgrBase {
    public Dictionary<int,ModelBase> dataDic = new Dictionary<int, ModelBase>();
    public virtual void Init(){
    }
    /// <summary>
    /// 从 xml 中加载数据并保存到字典里面.
    /// </summary>
    /// <param name="path">Path.</param>
    /// <param name="keys">Keys.</param>
    /// <param name="className">Class name.</param>
    protected void LoadFromXml(string path,string[] keys,string className,string[] dataTypes){
        XmlDocument xml = new XmlDocument ();
        xml.Load (path);
        XmlNode root= xml.SelectSingleNode ("root");
        XmlNodeList nodes = root.ChildNodes;
//      string[] dataTypes=GetDataType ((XmlElement)nodes [0]);
        for (int i = 1; i < nodes.Count; i++) {
            XmlElement item = nodes[i] as XmlElement;
            Assembly assembly = Assembly.GetExecutingAssembly();//获取命名空间
            ModelBase model= (ModelBase)assembly.CreateInstance (className);//实例化对象
            if (model == null)
                continue;
            System.Type type= model.GetType ();
            for (int j = 0; j < keys.Length; j++) {
                FieldInfo fi= type.GetField (keys [j]);
                object dataValue=null;
                switch (dataTypes[j]) {
                case "int":
                    dataValue = int.Parse (item.GetAttribute (keys [j]));
                    break;
                case "float":
                    dataValue = float.Parse (item.GetAttribute (keys [j]));
                    break;
                case "bool":
                    dataValue = bool.Parse (item.GetAttribute (keys [j]));
                    break;
                case "string":
                    dataValue =item.GetAttribute (keys [j]);
                    break;
                default:
                    break;
                }
                if(fi!=null){
                    fi.SetValue(model,dataValue);
                }
            }
            dataDic.Add (model.id,model);
        }
    }
//  string[] GetDataType(XmlElement element){
////        Debug.Log (bool.Parse("true"));
////        Debug.Log (float.Parse("1.23"));
//      string dt = element.GetAttribute ("dataType");
//      return dt.Split(',');
//  }
    /// <summary>
    /// 通过ID从集合中查找数据.
    /// </summary>
    /// <param name="id">Identifier.</param>
    /// <typeparam name="T">The 1st type parameter.</typeparam>
    public T GetDataById<T>(int id) where T:ModelBase{
        T model;
        ModelBase mb;
        dataDic.TryGetValue (id, out mb);
        if(dataDic.ContainsKey(id)){
            return (T)dataDic[id];
        }else return default(T);
    }
    /// <summary>
    /// 通过className创建实例对象.
    /// </summary>
    /// <returns>The instance by class name.</returns>
    /// <param name="className">Class name.</param>
    /// <typeparam name="T">The 1st type parameter.</typeparam>
    private T CreateInstanceByClassName<T>(string className){
        Assembly assembly = Assembly.GetExecutingAssembly();
        object obj= assembly.CreateInstance (className);
        //      obj.GetType().GetField("chapter").SetValue(obj,21); //获取指定名称的字段
        //      System.Reflection.PropertyInfo propertyInfo = type.GetProperty("chapter"); //获取指定名称的属性
        //      propertyInfo.SetValue(obj,21,null);
        return (T)obj;
    }
}

xml数据管理类  
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Xml;
using System.Reflection;
public class StoryMgr:ModelMgrBase  {
    public  class StoryEntry:ModelBase{
        public string chapter;
        public string index;
        public string content;
        public string hero;
        public string heroName;
        public bool type;
        public void show(){
            string str = "";
            //取得当前方法命名空间
            str += "命名空间名:" + System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Namespace + "\n";
            //取得当前方法类全名
            str += "类名:" + System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName + "\n";
            Debug.Log (str);
        }
    }
    private static StoryMgr instance;
    public static StoryMgr Instance{
        get
        { 
            if (instance == null)
                instance = new StoryMgr ();
            return instance;
        }
    }
 /// <summary>
 /// 初始化数据,然后假装xml,保存到字典集合中.
 /// </summary>
    public override void Init ()
    {
        base.Init ();
        string path=Application.streamingAssetsPath+"/xml/story.xml";
        string[] keys = new string[]{ "id","chapter","index","content","hero","heroName","type"};// xml 的属性名称
        string[] dataTypes = new string[]{"int","string","string","string","string","string","bool" };//对应的数据类型
        string className = "StoryMgr+StoryEntry"; //classname=命名空间+“.”+类全名,如果命名空间为空,只用添加类全名
        LoadFromXml (path,keys,className,dataTypes);
    }
}

最后,创建一个mono脚本,测试一下我们的方法  
using UnityEngine;
using System.Collections;
public class testScript : MonoBehaviour {
    // Use this for initialization
    void Start () {
        StoryMgr.Instance.Init ();
        StoryMgr.StoryEntry st=StoryMgr.Instance.GetDataById<StoryMgr.StoryEntry> (3);
        Debug.Log (st.id+"--"+st.content+"--"+st.type);
    }
    // Update is called once per frame
    void Update () {
    }
}
以后再解析其他xml的时候,只要创建好数据类,然后xml管理类直接继承xml解析类,就可以将xml的数据保存到数据字典里面。  
来自:https://blog.csdn.net/u011484013/article/details/73920806

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引