|
|
@@ -0,0 +1,532 @@
|
|
|
+using UnityEditor;
|
|
|
+using UnityEngine;
|
|
|
+using System;
|
|
|
+using System.Collections.Generic;
|
|
|
+using System.IO;
|
|
|
+using Excel;
|
|
|
+using System.Data;
|
|
|
+
|
|
|
+using System.Xml;
|
|
|
+using System.Threading;
|
|
|
+
|
|
|
+public class ExportLanguage : EditorWindow
|
|
|
+{
|
|
|
+ [MenuItem("QHJ/Design Tools/Export Language")]
|
|
|
+ public static void AddWindow()
|
|
|
+ {
|
|
|
+ ExportLanguage window = (ExportLanguage)EditorWindow.GetWindow(typeof(ExportLanguage), false, "Export Language");
|
|
|
+ window.Show();
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ public List<string> nameArray = new List<string>();
|
|
|
+
|
|
|
+ private string defaultPath;
|
|
|
+ private bool shouldDelete = true;
|
|
|
+ private bool isSuccess;
|
|
|
+ private string text;
|
|
|
+ private Dictionary<string, XmlNode> parentNodeDict;
|
|
|
+
|
|
|
+ private string lijiaUrl = "192.168.1.50";
|
|
|
+ private string localUrl = "192.168.1.41";
|
|
|
+ private string remoteUrl = "218.244.129.189";
|
|
|
+ private bool toLijia = false;
|
|
|
+ private bool toLocal = false;
|
|
|
+ private bool toRemote = false;
|
|
|
+ private string sourcePath = "";
|
|
|
+
|
|
|
+ void Awake()
|
|
|
+ {
|
|
|
+ defaultPath = Application.dataPath + @"/XlsxSource";
|
|
|
+ if (!Directory.Exists(defaultPath))
|
|
|
+ Directory.CreateDirectory(defaultPath);
|
|
|
+ GetObjectNameToArray<string>(defaultPath, "xlsx");
|
|
|
+ }
|
|
|
+
|
|
|
+ void OnGUI()
|
|
|
+ {
|
|
|
+ GUILayout.Label(new GUIContent("1、导出所有语言文件到language目录"));
|
|
|
+
|
|
|
+ shouldDelete = GUILayout.Toggle(shouldDelete, new GUIContent("覆盖已有文件"));
|
|
|
+
|
|
|
+ if (GUILayout.Button("立即导出", GUILayout.Height(30)))
|
|
|
+ CreateAllXml();
|
|
|
+
|
|
|
+ GUILayout.Space(10);
|
|
|
+ GUILayout.Label(new GUIContent("2、生成并上传服务器用语言文件"));
|
|
|
+ if (GUILayout.Button("生成服务器语言文件", GUILayout.Height(30)))
|
|
|
+ CreateAllPhp();
|
|
|
+
|
|
|
+ GUILayout.Space(10);
|
|
|
+
|
|
|
+ GUILayout.Label("选择要上传语言配置的服务器,可多选", GUILayout.Height(20));
|
|
|
+ toLijia = GUILayout.Toggle(toLijia, "上传到李嘉服务器");
|
|
|
+ toLocal = GUILayout.Toggle(toLocal, "上传到内网服务器");
|
|
|
+ GUILayout.BeginHorizontal();
|
|
|
+ toRemote = GUILayout.Toggle(toRemote, "上传到外网服务器");
|
|
|
+ GUILayout.ExpandWidth(false);
|
|
|
+ GUILayout.Label("[url]" + remoteUrl);
|
|
|
+ GUILayout.EndHorizontal();
|
|
|
+ GUILayout.BeginHorizontal();
|
|
|
+ if (GUILayout.Button("全选", GUILayout.Width(40), GUILayout.Height(20)))
|
|
|
+ {
|
|
|
+ toLijia = true;
|
|
|
+ toLocal = true;
|
|
|
+ toRemote = true;
|
|
|
+ }
|
|
|
+ if (GUILayout.Button("清空", GUILayout.Width(40), GUILayout.Height(20)))
|
|
|
+ {
|
|
|
+ toLijia = false;
|
|
|
+ toLocal = false;
|
|
|
+ toRemote = false;
|
|
|
+ }
|
|
|
+ GUILayout.EndHorizontal();
|
|
|
+
|
|
|
+ if (GUILayout.Button("上传配置到服务器", GUILayout.Height(30)))
|
|
|
+ {
|
|
|
+ sourcePath = Application.dataPath + @"/XlsxSource";
|
|
|
+ Dictionary<string, LanguageUploadData> data = new Dictionary<string, LanguageUploadData>();
|
|
|
+ if (toLijia)
|
|
|
+ {
|
|
|
+ LanguageUploadData data0 = new LanguageUploadData();
|
|
|
+ data0.targetUrl = lijiaUrl;
|
|
|
+ data0.sourcePath = sourcePath;
|
|
|
+ if (!data.ContainsKey(lijiaUrl))
|
|
|
+ data.Add(lijiaUrl, data0);
|
|
|
+ }
|
|
|
+ if (toLocal)
|
|
|
+ {
|
|
|
+ LanguageUploadData data1 = new LanguageUploadData();
|
|
|
+ data1.targetUrl = localUrl;
|
|
|
+ data1.sourcePath = sourcePath;
|
|
|
+ if (!data.ContainsKey(localUrl))
|
|
|
+ data.Add(localUrl, data1);
|
|
|
+ }
|
|
|
+ if (toRemote)
|
|
|
+ {
|
|
|
+ LanguageUploadData data2 = new LanguageUploadData();
|
|
|
+ data2.targetUrl = remoteUrl;
|
|
|
+ data2.sourcePath = sourcePath;
|
|
|
+ if (!data.ContainsKey(remoteUrl))
|
|
|
+ data.Add(remoteUrl, data2);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!toLijia && !toLocal && !toRemote)
|
|
|
+ ShowNotification(new GUIContent("请至少选择一个上传地址"));
|
|
|
+
|
|
|
+ new LanguageUploadFiles(data);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Return all files' name in target path in Assets//
|
|
|
+ /// </summary>
|
|
|
+ /// <returns>File name in array</returns>
|
|
|
+ /// <param name="path">Assets sublevel path</param>
|
|
|
+ /// <param name="pattern">File type filter</param>
|
|
|
+ /// <typeparam name="T">Class name</typeparam>
|
|
|
+ void GetObjectNameToArray<T>(string path, string pattern, string subDirectory = "")
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ //return file name by array in target folder & subfolder, it can be null
|
|
|
+ string[] files = Directory.GetFiles(path);
|
|
|
+
|
|
|
+ for (int i = 0; i < files.Length; i++)
|
|
|
+ {
|
|
|
+ string p = files[i];
|
|
|
+
|
|
|
+ //file
|
|
|
+ int index = p.LastIndexOf("\\");
|
|
|
+ string folder = p.Substring(0, index + 1);
|
|
|
+ string fileName = p.Substring(index + 1);
|
|
|
+
|
|
|
+ //if directoryEntries is not null, tempPaths cannot be null after splited
|
|
|
+ if (fileName.EndsWith(".meta"))
|
|
|
+ continue;
|
|
|
+
|
|
|
+ string[] pathSplit = StringExtention.SplitWithString(fileName, ".");
|
|
|
+
|
|
|
+ if (pathSplit.Length > 1 && pathSplit[0].Contains("language"))
|
|
|
+ {
|
|
|
+ nameArray.Add(subDirectory + "\\" + pathSplit[0]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ //recursion
|
|
|
+ string[] folders = Directory.GetDirectories(path);
|
|
|
+ for (int i = 0; i < folders.Length; i++)
|
|
|
+ {
|
|
|
+ string p = folders[i];
|
|
|
+ GetObjectNameToArray<T>(p, pattern, p.Substring(defaultPath.Length));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ catch (System.IO.DirectoryNotFoundException)
|
|
|
+ {
|
|
|
+ Debug.Log("The path encapsulated in the " + path + "Directory object does not exist.");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// custom split string function
|
|
|
+ /// </summary>
|
|
|
+ private class StringExtention
|
|
|
+ {
|
|
|
+
|
|
|
+ public static string[] SplitWithString(string sourceString, string splitString)
|
|
|
+ {
|
|
|
+ string tempSourceString = sourceString;
|
|
|
+ List<string> arrayList = new List<string>();
|
|
|
+ string s = string.Empty;
|
|
|
+ while (sourceString.IndexOf(splitString) > -1) //split
|
|
|
+ {
|
|
|
+ s = sourceString.Substring(0, sourceString.IndexOf(splitString));
|
|
|
+ sourceString = sourceString.Substring(sourceString.IndexOf(splitString) + splitString.Length);
|
|
|
+ arrayList.Add(s);
|
|
|
+ }
|
|
|
+ arrayList.Add(sourceString);
|
|
|
+ return arrayList.ToArray();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void CreateAllXml()
|
|
|
+ {
|
|
|
+ foreach (string str in nameArray)
|
|
|
+ {
|
|
|
+ if (!str.Contains("language"))
|
|
|
+ continue;
|
|
|
+
|
|
|
+ CreateXml(str);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isSuccess)
|
|
|
+ {
|
|
|
+ ShowNotification(new GUIContent( "已成功导出!"));
|
|
|
+ DirectoryInfo di = new DirectoryInfo(defaultPath);
|
|
|
+ DirectoryInfo[] diArr = di.GetDirectories();
|
|
|
+ isSuccess = false;
|
|
|
+ AssetDatabase.Refresh();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ ShowNotification(new GUIContent("文件有错误!"));
|
|
|
+ }
|
|
|
+
|
|
|
+ private void CreateXml(string fileName)
|
|
|
+ {
|
|
|
+ parentNodeDict = new Dictionary<string, XmlNode>();
|
|
|
+
|
|
|
+ string realName = StringExtention.SplitWithString(fileName, "\\")[2];
|
|
|
+ string filepath = Application.dataPath + @"/Resources/XML/Config/All/" + realName + ".xml";
|
|
|
+ text = "";
|
|
|
+
|
|
|
+ filepath = filepath.Replace("\\", "/");
|
|
|
+ string[] pathArr = filepath.Split('/');
|
|
|
+ string tempPath = "";
|
|
|
+ for(int i = 0; i < pathArr.Length; i++)
|
|
|
+ {
|
|
|
+ tempPath += pathArr[i] + "/";
|
|
|
+ if (i > 1 && i < pathArr.Length - 1)
|
|
|
+ {
|
|
|
+ if (!Directory.Exists(tempPath))
|
|
|
+ {
|
|
|
+ Directory.CreateDirectory(tempPath);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((!File.Exists(filepath) && !shouldDelete) || shouldDelete)
|
|
|
+ {
|
|
|
+ XmlDocument xmlDoc = new XmlDocument();
|
|
|
+ tt(fileName, filepath, xmlDoc);
|
|
|
+
|
|
|
+ //try
|
|
|
+ //{
|
|
|
+ // tt(fileName, filepath, xmlDoc);
|
|
|
+ //}
|
|
|
+ //catch (Exception e)
|
|
|
+ //{
|
|
|
+ // text += "Exception " + e.Message;
|
|
|
+ // Debug.Log("Exception " + e.Message);
|
|
|
+ //}
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void tt(string fileName, string filepath, XmlDocument xmlDoc)
|
|
|
+ {
|
|
|
+ FileStream stream = File.Open(Application.dataPath + @"/XlsxSource" + fileName + ".xlsx", FileMode.Open, FileAccess.Read);
|
|
|
+ IExcelDataReader excelReader = ExcelReaderFactory.CreateOpenXmlReader(stream);
|
|
|
+
|
|
|
+ text += "\nexcelReader.ResultsCount is" + excelReader.ResultsCount + "\n";
|
|
|
+
|
|
|
+ text += "start excelReader. \n";
|
|
|
+
|
|
|
+ DataSet result = excelReader.AsDataSet();
|
|
|
+
|
|
|
+ text += "get result successful? result[" + result + "]";
|
|
|
+
|
|
|
+ text += "result columns count is " + result.Tables[0].Columns.Count;
|
|
|
+
|
|
|
+ int columns = result.Tables[0].Columns.Count;
|
|
|
+ int rows = result.Tables[0].Rows.Count;
|
|
|
+
|
|
|
+ //start create xml
|
|
|
+ XmlElement root = xmlDoc.CreateElement("lan");
|
|
|
+ XmlNode currentChildNode = null;
|
|
|
+ string parentNodeName = "";
|
|
|
+ for (int i = 0; i < rows; i++)
|
|
|
+ {
|
|
|
+ if (i == 0)
|
|
|
+ {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (int j = 0; j < columns; j++)
|
|
|
+ {
|
|
|
+ string nvalue = result.Tables[0].Rows[i][j].ToString();
|
|
|
+
|
|
|
+ if (i > 2)
|
|
|
+ {
|
|
|
+ switch (j)
|
|
|
+ {
|
|
|
+ case 0:
|
|
|
+ if (!parentNodeDict.ContainsKey(nvalue))
|
|
|
+ {
|
|
|
+ if (nvalue == "")
|
|
|
+ {
|
|
|
+ Debug.LogError("第" + (i + 1) + "行有错误" + "\nxmlDoc" + xmlDoc + "\n" + nvalue + "ij" + i + " " + j);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ XmlNode node = xmlDoc.CreateElement(nvalue);
|
|
|
+ parentNodeDict.Add(nvalue, node);
|
|
|
+ }
|
|
|
+ parentNodeName = nvalue;
|
|
|
+ break;
|
|
|
+ case 1:
|
|
|
+ if (nvalue == "")
|
|
|
+ {
|
|
|
+ Debug.LogError("第"+(i+1)+"行有错误"+"\nxmlDoc" + xmlDoc + "\n" + nvalue + "ij" + i + " " + j);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ currentChildNode = xmlDoc.CreateElement(nvalue);
|
|
|
+ parentNodeDict[parentNodeName].AppendChild(currentChildNode);
|
|
|
+
|
|
|
+ break;
|
|
|
+ case 2:
|
|
|
+ XmlCDataSection cData = xmlDoc.CreateCDataSection(nvalue);
|
|
|
+ currentChildNode.AppendChild(cData);
|
|
|
+ break;
|
|
|
+ case 3:
|
|
|
+ XmlAttribute comment = xmlDoc.CreateAttribute("comm");
|
|
|
+ comment.Value = nvalue;
|
|
|
+ currentChildNode.Attributes.Append(comment);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ //Debug.Log("labels[j] is " + labels[j] + ", nvalue is " + nvalue);
|
|
|
+ }
|
|
|
+ //Debug.Log(nvalue);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach (KeyValuePair<string, XmlNode> keyValue in parentNodeDict)
|
|
|
+ root.AppendChild(keyValue.Value);
|
|
|
+
|
|
|
+ xmlDoc.AppendChild(root);
|
|
|
+ xmlDoc.Save(filepath);
|
|
|
+ AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
|
|
|
+ // AssetDatabase.ImportAsset(@"Assets/Resources/XML/Config" + fileName + ".xml", ImportAssetOptions.ForceUpdate);
|
|
|
+ //Debug.Log(fileName + ".xml is saved to " + filepath + ", count is " + idArr.Count);
|
|
|
+
|
|
|
+ isSuccess = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void CreateAllPhp()
|
|
|
+ {
|
|
|
+ foreach (string str in nameArray)
|
|
|
+ {
|
|
|
+ if (!str.Contains("language"))
|
|
|
+ continue;
|
|
|
+
|
|
|
+ CreatePhp(str);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isSuccess)
|
|
|
+ {
|
|
|
+ ShowNotification(new GUIContent("已成功导出!"));
|
|
|
+ DirectoryInfo di = new DirectoryInfo(defaultPath);
|
|
|
+ DirectoryInfo[] diArr = di.GetDirectories();
|
|
|
+ isSuccess = false;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ ShowNotification(new GUIContent("文件有错误!"));
|
|
|
+ }
|
|
|
+
|
|
|
+ private void CreatePhp(string fileName)
|
|
|
+ {
|
|
|
+ string realName = StringExtention.SplitWithString(fileName, "\\")[1];
|
|
|
+ string filepath = defaultPath + "/" + realName + ".php";
|
|
|
+ text = "";
|
|
|
+
|
|
|
+ filepath = filepath.Replace("\\", "/");
|
|
|
+ string[] pathArr = filepath.Split('/');
|
|
|
+ string tempPath = "";
|
|
|
+ for (int i = 0; i < pathArr.Length; i++)
|
|
|
+ {
|
|
|
+ tempPath += pathArr[i] + "/";
|
|
|
+ if (i > 1 && i < pathArr.Length - 1)
|
|
|
+ {
|
|
|
+ if (!Directory.Exists(tempPath))
|
|
|
+ {
|
|
|
+ Directory.CreateDirectory(tempPath);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((!File.Exists(filepath) && !shouldDelete) || shouldDelete)
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ string fullText = "<?php\n";
|
|
|
+
|
|
|
+ FileStream stream = File.Open(Application.dataPath + @"/XlsxSource" + fileName + ".xlsx", FileMode.Open, FileAccess.Read);
|
|
|
+ IExcelDataReader excelReader = ExcelReaderFactory.CreateOpenXmlReader(stream);
|
|
|
+
|
|
|
+ text += "\nexcelReader.ResultsCount is" + excelReader.ResultsCount + "\n";
|
|
|
+
|
|
|
+ text += "start excelReader. \n";
|
|
|
+
|
|
|
+ DataSet result = excelReader.AsDataSet();
|
|
|
+
|
|
|
+ text += "get result successful? result[" + result + "]";
|
|
|
+
|
|
|
+ text += "result columns count is " + result.Tables[0].Columns.Count;
|
|
|
+
|
|
|
+ int columns = result.Tables[0].Columns.Count;
|
|
|
+ int rows = result.Tables[0].Rows.Count;
|
|
|
+
|
|
|
+ //start create xml
|
|
|
+ for (int i = 0; i < rows; i++)
|
|
|
+ {
|
|
|
+ if (i == 0)
|
|
|
+ {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (int j = 0; j < columns; j++)
|
|
|
+ {
|
|
|
+ string nvalue = result.Tables[0].Rows[i][j].ToString();
|
|
|
+
|
|
|
+ if (i > 2)
|
|
|
+ {
|
|
|
+ switch (j)
|
|
|
+ {
|
|
|
+ case 0:
|
|
|
+ fullText += "$LANG['" + nvalue + "|";
|
|
|
+ break;
|
|
|
+ case 1:
|
|
|
+ fullText += nvalue + "'] = '";
|
|
|
+ break;
|
|
|
+ case 2:
|
|
|
+ nvalue = nvalue.Replace("\'", "\\\'");
|
|
|
+ fullText += nvalue + "\';\n";
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ fullText += "?>";
|
|
|
+ StreamWriter sw = new StreamWriter(filepath);
|
|
|
+ string w = fullText;
|
|
|
+ sw.Write(w);
|
|
|
+ sw.Close();
|
|
|
+ AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
|
|
|
+
|
|
|
+ isSuccess = true;
|
|
|
+ }
|
|
|
+ catch (Exception e)
|
|
|
+ {
|
|
|
+ text += "Exception " + e.Message;
|
|
|
+ Debug.Log("Exception " + e.Message);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void HideNotification()
|
|
|
+ {
|
|
|
+ this.RemoveNotification();
|
|
|
+ }
|
|
|
+
|
|
|
+ void OnInspectorUpdate()
|
|
|
+ {
|
|
|
+ this.Repaint();
|
|
|
+ }
|
|
|
+
|
|
|
+ void OnDestroy()
|
|
|
+ {
|
|
|
+ EditorUtility.UnloadUnusedAssetsImmediate();
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+public class LanguageUploadData
|
|
|
+{
|
|
|
+ public string sourcePath;
|
|
|
+ public string targetUrl;
|
|
|
+
|
|
|
+ public string GetCmd()
|
|
|
+ {
|
|
|
+ string command = "@echo off\r\n" +
|
|
|
+ "echo open " + targetUrl + ">ftp.up\r\n" +
|
|
|
+ "echo gaoyuqin>>ftp.up\r\n" +
|
|
|
+ "echo Gaoyuqin123654>>ftp.up\r\n" +
|
|
|
+ //"echo Cd .\\User >>ftp.up\r\n" +
|
|
|
+ "echo binary>>ftp.up\r\n" +
|
|
|
+ "echo lcd \"" + sourcePath + "\">>ftp.up\r\n" +
|
|
|
+ "echo prompt>>ftp.up\r\n" +
|
|
|
+ "echo mkdir qwsk/lang/>>ftp.up\r\n" +
|
|
|
+ "echo cd qwsk/lang/>>ftp.up\r\n" +
|
|
|
+ "echo mput *.php>>ftp.up\r\n" +
|
|
|
+ "echo bye>>ftp.up\r\n" +
|
|
|
+ "FTP -s:ftp.up\r\n" +
|
|
|
+ "del ftp.up /q\r\n" +
|
|
|
+ "pause\r\n";
|
|
|
+
|
|
|
+ return command;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+public class LanguageUploadFiles
|
|
|
+{
|
|
|
+ private Dictionary<string, LanguageUploadData> data;
|
|
|
+
|
|
|
+ private Thread s;
|
|
|
+ public LanguageUploadFiles(Dictionary<string, LanguageUploadData> data)
|
|
|
+ {
|
|
|
+ this.data = data;
|
|
|
+ s = new Thread(Run);
|
|
|
+ s.Start();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void Run()
|
|
|
+ {
|
|
|
+ foreach (string key in data.Keys)
|
|
|
+ {
|
|
|
+ LanguageUploadData uploadData = data[key];
|
|
|
+ RunCmd(key, uploadData.GetCmd());
|
|
|
+ Debug.Log("【RunCmd】" + uploadData.sourcePath + " upload to---- 【 " + uploadData.targetUrl + " 】");
|
|
|
+ }
|
|
|
+ s.Abort();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void RunCmd(string key, string command)
|
|
|
+ {
|
|
|
+ Debug.Log(command);
|
|
|
+ File.WriteAllText(key + "-upload-language.bat", command, System.Text.Encoding.GetEncoding(936));
|
|
|
+ System.Diagnostics.Process.Start(key + "-upload-language.bat");
|
|
|
+ }
|
|
|
+}
|