using System; using System.Collections; using System.IO; using System.Linq; using System.Xml; using System.Xml.Linq; using System.Xml.Serialization; using UnityEngine; using UnityEngine.Networking; namespace UVC.Util { /// /// XML 데이터 처리 유틸리티 클래스 /// public class XmlDataUtil : MonoBehaviour { private static XmlDataUtil __instance = null; public static XmlDataUtil Instance { get { if(__instance == null) { __instance = new GameObject("_XmlDataUtil").AddComponent(); } return __instance; } } private void Awake() { DontDestroyOnLoad(gameObject); } /// /// 데이터 직렬화 + XML 파일 저장 /// /// 저장될 파일 경로 /// 직렬화할 데이터 /// /// /// var data = new MyData { Name = "Test", Age = 25 }; /// XmlDataUtil.Instance.SerializeXmlData("path/to/file.xml", data); /// /// public void SerializeXmlData(string filePath, object data) { XmlSerializer serializer = new XmlSerializer(data.GetType()); using (TextWriter writer = new StreamWriter(filePath)) { serializer.Serialize(writer, data); } } /// /// 데이터 비직렬화 + XML 파일 로드 /// /// 비직렬화할 데이터 타입 /// XML 파일 경로 /// 로드 완료 후 실행할 콜백 /// /// /// XmlDataUtil.Instance.DeserializeXmlData("path/to/file.xml", (data) => /// { /// Debug.Log($"Name: {data.Name}, Age: {data.Age}"); /// }); /// /// public void DeserializeXmlData(string filePath, Action completeAction) { XmlSerializer serializer = new XmlSerializer(typeof(T)); using (FileStream fs = new FileStream(filePath, FileMode.Open)) { XmlReader reader = XmlReader.Create(fs); if (completeAction != null) { completeAction.Invoke((T)serializer.Deserialize(reader)); } } } /// /// XML 데이터 저장 /// /// 저장될 파일 경로 /// 저장할 데이터 /// /// /// var data = new MyData { Name = "Test", Age = 25 }; /// XmlDataUtil.Instance.SaveXmlData("path/to/file.xml", data); /// /// public void SaveXmlData(string filePath, object data) { if (data == null) return; //파일이 있으면 기존 파일 로드 후 수정 if(File.Exists(filePath)) { StartCoroutine(LoadXml(filePath, (XDocument xdoc) => { UpdateData(xdoc, data, (isChanged, isDataError) => { if (isChanged && !isDataError) { xdoc.Save(filePath); Debug.Log("SaveXmlData!"); } else if (isDataError) { Debug.Log("XML 데이터 노드 순서가 잘못되었다.! 다시 강제로 직렬화한다."); SerializeXmlData(filePath, data); } }); })); } //파일이 없으면 새롭게 직렬화, 저장 else { SerializeXmlData(filePath, data); } } /// /// XML 데이터 로드 /// /// XML 파일 경로 /// 로드 완료 이벤트 핸들러 /// 에러 핸들러 /// 코루틴 /// /// /// StartCoroutine(XmlDataUtil.Instance.LoadXml("path/to/file.xml", (xdoc) => /// { /// Debug.Log(xdoc.ToString()); /// }, (error) => /// { /// Debug.LogError(error); /// })); /// /// public IEnumerator LoadXml(string filePath, Action OnLoadComplete, Action OnError = null) { if (!filePath.Contains("://")) { filePath = "file://" + filePath; } using (UnityWebRequest www = UnityWebRequest.Get(filePath)) { www.timeout = 5;//5초 yield return www.SendWebRequest(); if (www.isNetworkError || www.isHttpError) { if (OnError != null) { OnError(www.error); } } else { using (MemoryStream memStream = new MemoryStream()) { memStream.Write(www.downloadHandler.data, 0, www.downloadHandler.data.Length); memStream.Position = 0; XmlReader reader = XmlReader.Create(memStream); XDocument xdoc = XDocument.Load(reader); if (OnLoadComplete != null) { OnLoadComplete(xdoc); } } } } } /// /// XML 데이터를 비직렬화하여 로드합니다. /// /// 비직렬화할 데이터 타입 /// XML 파일 경로 /// 로드 완료 후 실행할 콜백 (비직렬화된 데이터 전달) /// 에러 발생 시 실행할 콜백 (에러 메시지 전달) /// 코루틴 /// /// /// StartCoroutine(XmlDataUtil.Instance.LoadXml("path/to/file.xml", (data) => /// { /// Debug.Log($"Name: {data.Name}, Age: {data.Age}"); /// }, (error) => /// { /// Debug.LogError($"Error loading XML: {error}"); /// })); /// /// [Obsolete] public IEnumerator LoadXml(string filePath, Action OnLoadComplete, Action OnError = null) { if (!filePath.Contains("://")) { filePath = "file://" + filePath; } using (UnityWebRequest www = UnityWebRequest.Get(filePath)) { www.timeout = 5;//5초 yield return www.SendWebRequest(); if (www.isNetworkError || www.isHttpError) { if (OnError != null) { OnError(www.error); } } else { using (MemoryStream memStream = new MemoryStream()) { memStream.Write(www.downloadHandler.data, 0, www.downloadHandler.data.Length); memStream.Position = 0; XmlSerializer serializer = new XmlSerializer(typeof(T)); XmlReader reader = XmlReader.Create(memStream); T result = (T)serializer.Deserialize(reader); if (OnLoadComplete != null) { OnLoadComplete(result); } } } } } private void UpdateData(XDocument xdoc, object data, Action OnComplete) { XmlSerializer xmlSerializer = new XmlSerializer(data.GetType()); using (StringWriter textWriter = new StringWriter()) { xmlSerializer.Serialize(textWriter, data); var currentData = XElement.Parse(textWriter.ToString()); bool isChanged = false; bool isDataError = false; var currentItemList = currentData.Descendants().ToList(); var originItemList = xdoc.Root.Descendants().ToList(); foreach (var currentItem in currentItemList) { foreach (var originItem in originItemList) { if (currentItemList.IndexOf(currentItem) == originItemList.IndexOf(originItem)) { if (currentItem.Name.Equals(originItem.Name)) { if (!currentItem.Value.Equals(originItem.Value) && !originItem.HasElements) { originItem.SetValue(currentItem.Value); isChanged = true; } foreach (var currentAtt in currentItem.Attributes()) { foreach (var originAtt in originItem.Attributes()) { if (!currentAtt.Value.Equals(originAtt.Value)) { originAtt.SetValue(currentAtt.Value); isChanged = true; } } } } else { isDataError = true; break; } } } } if(OnComplete != null) { OnComplete(isChanged, isDataError); } } } /// /// XML 데이터를 문자열로 직렬화 /// /// 직렬화할 객체 /// XML 문자열 /// /// /// var xmlString = XmlDataUtil.ToXmlString(new MyData { Name = "Test", Age = 25 }); /// Debug.Log(xmlString); /// /// public static string ToXmlString(object toSerialize) { XmlSerializer xmlSerializer = new XmlSerializer(toSerialize.GetType()); using (StringWriter textWriter = new StringWriter()) { xmlSerializer.Serialize(textWriter, toSerialize); return textWriter.ToString(); } } } }