Advertisement
AyrA

IniSerializer.cs

Sep 20th, 2015
287
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 18.31 KB | None | 0 0
  1. using System;
  2. using System.IO;
  3. using System.Reflection;
  4. using System.Runtime.Serialization;
  5. using System.Collections;
  6. using System.Text;
  7. using System.Collections.Generic;
  8.  
  9. namespace HumbleAPI
  10. {
  11.     /// <summary>
  12.     /// (De-)serializes objects into ini files
  13.     /// </summary>
  14.     public class IniFormatter : IFormatter
  15.     {
  16.         /// <summary>
  17.         /// An entry in an array.
  18.         /// Required to sort array entries if somebody shuffles them in the ini file
  19.         /// </summary>
  20.         private class ArrayEntry
  21.         {
  22.             /// <summary>
  23.             /// Array index
  24.             /// </summary>
  25.             public int Index
  26.             { get; private set; }
  27.             /// <summary>
  28.             /// Array value
  29.             /// </summary>
  30.             public string Value
  31.             { get; private set; }
  32.             /// <summary>
  33.             /// Array name
  34.             /// </summary>
  35.             public string Name
  36.             { get; private set; }
  37.  
  38.             /// <summary>
  39.             /// Creates a new array entry
  40.             /// </summary>
  41.             /// <param name="Line">INI entry</param>
  42.             public ArrayEntry(string Line)
  43.             {
  44.                 string[] Parts = new string[]
  45.                     {
  46.                         Line.Substring(0,Line.IndexOf('=')),
  47.                         Line.Substring(Line.IndexOf('=')+1)
  48.                     };
  49.  
  50.                 if (Parts.Length < 2)
  51.                 {
  52.                     throw new ArgumentException("Invalid INI file line");
  53.                 }
  54.                 Value = Parts[1];
  55.                 Name = Parts[0].Substring(0, Parts[0].IndexOf('['));
  56.                 //extracts the index
  57.                 Index = int.Parse(Parts[0].Substring(Parts[0].IndexOf('[') + 1,
  58.                     Parts[0].Length - Parts[0].IndexOf('[') - 2));
  59.             }
  60.  
  61.             /// <summary>
  62.             /// Converts the entry back to its ini line
  63.             /// </summary>
  64.             /// <returns>string</returns>
  65.             public override string ToString()
  66.             {
  67.                 return string.Format("{0}[{1}]={2}", Name, Index, Value);
  68.             }
  69.         }
  70.  
  71.         /// <summary>
  72.         /// Possible types of lines in an ini file
  73.         /// </summary>
  74.         private enum LineType
  75.         {
  76.             /// <summary>
  77.             /// A [Section]
  78.             /// </summary>
  79.             Section,
  80.             /// <summary>
  81.             /// A Property=Value
  82.             /// </summary>
  83.             Property,
  84.             /// <summary>
  85.             /// A #comment
  86.             /// </summary>
  87.             Comment,
  88.             /// <summary>
  89.             /// an empty line
  90.             /// </summary>
  91.             Empty,
  92.             /// <summary>
  93.             /// An invalid line
  94.             /// </summary>
  95.             Invalid
  96.         }
  97.  
  98.         /// <summary>
  99.         /// null reference string (not empty string)
  100.         /// </summary>
  101.         public const string NULL = "[null]";
  102.         /// <summary>
  103.         /// base64 encoded string
  104.         /// </summary>
  105.         public const string B64 = "B64:";
  106.         /// <summary>
  107.         /// array of values
  108.         /// </summary>
  109.         public const string ARRAY = "ARRAY:";
  110.  
  111.         public ISurrogateSelector SurrogateSelector
  112.         { get; set; }
  113.         public SerializationBinder Binder
  114.         { get; set; }
  115.         public StreamingContext Context
  116.         { get;set; }
  117.  
  118.         /// <summary>
  119.         /// chars that cause a string to get Base64 encoded
  120.         /// </summary>
  121.         private string[] replaceables = new string[]
  122.             {
  123.                 @"\",
  124.                 "\0",
  125.                 "\r",
  126.                 "\n",
  127.                 "\t",
  128.             };
  129.         /// <summary>
  130.         /// Creates a new IniFormatter
  131.         /// </summary>
  132.         public IniFormatter()
  133.         {
  134.             Context = new StreamingContext(StreamingContextStates.All);
  135.         }
  136.  
  137.         /// <summary>
  138.         /// Deserializes an object from an ini file
  139.         /// </summary>
  140.         /// <param name="serializationStream">Stream to ini data</param>
  141.         /// <returns>deserialized object</returns>
  142.         public object Deserialize(Stream serializationStream)
  143.         {
  144.             string[] Content;
  145.             using (StreamReader SR = new StreamReader(serializationStream))
  146.             {
  147.                 Content = SR.ReadToEnd().Split(new string[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries);
  148.             }
  149.  
  150.             return AssembleSection(Content[0].Substring(1, Content[0].Length - 2), Content);
  151.         }
  152.  
  153.         /// <summary>
  154.         /// Assembles a section into an object
  155.         /// </summary>
  156.         /// <param name="Section">Section name</param>
  157.         /// <param name="Content">INI lines</param>
  158.         /// <returns>assembled object</returns>
  159.         private object AssembleSection(string Section, string[] Content)
  160.         {
  161.             string TypeName = extractType(Section);
  162.             //get all values from that object
  163.             string[] Values = GetSection(Section, Content);
  164.             //get the type of the current object
  165.             Type T = Type.GetType(TypeName);
  166.             object obj = FormatterServices.GetUninitializedObject(T);
  167.             MemberInfo[] MI = FormatterServices.GetSerializableMembers(T);
  168.             object[] data = new object[MI.Length];
  169.             Dictionary<string, string> sdict = new Dictionary<string, string>();
  170.             //get all properties
  171.             foreach (string Line in Values)
  172.             {
  173.                 string[] Parts = new string[]
  174.                 {
  175.                     Line.Substring(0,Line.IndexOf('=')),
  176.                     Line.Substring(Line.IndexOf('=')+1)
  177.                 };
  178.                 //line is an array
  179.                 if (Parts[0].Contains("["))
  180.                 {
  181.                     //only process, if first array entry
  182.                     if (Parts[0].EndsWith("[0]"))
  183.                     {
  184.                         string Name = new ArrayEntry(Line).Name;
  185.                         sdict.Add(Name, ARRAY + string.Join("|", GetArrayLine(Name, Values)));
  186.                     }
  187.                 }
  188.                 else
  189.                 {
  190.                     sdict.Add(Parts[0], Parts[1]);
  191.                 }
  192.             }
  193.  
  194.             for(int i=0;i<MI.Length;i++)
  195.             {
  196.                 FieldInfo FI = (FieldInfo)MI[i];
  197.                 if (!sdict.ContainsKey(FI.Name))
  198.                 {
  199.                     throw new SerializationException("Missing field value: " + FI.Name);
  200.                 }
  201.                 if (FI.FieldType.IsArray)
  202.                 {
  203.                     object[] oArr=(object[])FromString(sdict[FI.Name], Content);
  204.                     if (oArr != null)
  205.                     {
  206.                         Array A = Array.CreateInstance(oArr[0].GetType(), oArr.Length);
  207.                         Array.Copy(oArr, A, A.Length);
  208.                         data[i] = A;
  209.                     }
  210.                     else
  211.                     {
  212.                         data[i] = null;
  213.                     }
  214.                 }
  215.                 else
  216.                 {
  217.                     data[i] = System.Convert.ChangeType(FromString(sdict[FI.Name], Content), FI.FieldType);
  218.                 }
  219.             }
  220.  
  221.             return FormatterServices.PopulateObjectMembers(obj, MI, data);
  222.         }
  223.  
  224.         /// <summary>
  225.         /// Serializes data into an ini file
  226.         /// </summary>
  227.         /// <param name="serializationStream">INI output stream</param>
  228.         /// <param name="graph">Object</param>
  229.         public void Serialize(Stream serializationStream, object graph)
  230.         {
  231.             using (StreamWriter sw = new StreamWriter(serializationStream))
  232.             {
  233.                 string s = Serialize(graph.GetType().FullName, graph);
  234.                 sw.WriteLine(s);
  235.                 sw.Flush();
  236.             }
  237.         }
  238.  
  239.         /// <summary>
  240.         /// Serializes an object and its members into a section
  241.         /// </summary>
  242.         /// <param name="current">Current Section</param>
  243.         /// <param name="graph">Object</param>
  244.         /// <returns>string with content</returns>
  245.         private string Serialize(string current, object graph)
  246.         {
  247.             StringBuilder Current = new StringBuilder();
  248.             StringBuilder Postcontent = new StringBuilder();
  249.             MemberInfo[] members = FormatterServices.GetSerializableMembers(graph.GetType(), Context);
  250.             object[] objs = FormatterServices.GetObjectData(graph, members);
  251.  
  252.             Current.AppendFormat("[{0}]\r\n", current);
  253.             for (int i = 0; i < objs.Length; ++i)
  254.             {
  255.                 if (objs[i] == null)
  256.                 {
  257.                     Current.AppendFormat("{0}={1}\r\n", members[i].Name, NULL);
  258.                 }
  259.                 else
  260.                 {
  261.                     FieldInfo FI = (FieldInfo)members[i];
  262.                     if (!FI.FieldType.IsPrimitive && FI.FieldType.FullName != string.Empty.GetType().FullName)
  263.                     {
  264.                         //not a primitive type (and no string)
  265.                         //check if it is enumerable
  266.                         if (objs[i] is IEnumerable)
  267.                         {
  268.                             int j = 0;
  269.                             foreach (object o in ((IEnumerable)objs[i]))
  270.                             {
  271.                                 if (o.GetType().IsPrimitive || FI.FieldType.FullName == string.Empty.GetType().FullName)
  272.                                 {
  273.                                     Current.AppendFormat("{0}[{1}]={2}\r\n", members[i].Name, j, o == null ? NULL : o.ToString());
  274.                                 }
  275.                                 else
  276.                                 {
  277.                                     Current.AppendFormat("{0}[{1}]=[{2}]\r\n", members[i].Name, j, string.Format("{0},{1}[{2}]", current, FI.FieldType.FullName, j));
  278.                                     Postcontent.Append(Serialize(string.Format("{0},{1}[{2}]", current, FI.FieldType.FullName, j), o));
  279.                                 }
  280.                                 j++;
  281.                             }
  282.                             if (j == 0)
  283.                             {
  284.                                 Current.AppendFormat("{0}={1}\r\n", members[i].Name, NULL);
  285.                             }
  286.                         }
  287.                         else
  288.                         {
  289.                             Current.AppendFormat("{0}=[{1},{2}]\r\n", members[i].Name, current, FI.FieldType.FullName);
  290.                             Postcontent.Append(Serialize(string.Format("{0},{1}", current, FI.FieldType.FullName), objs[i]));
  291.                         }
  292.                     }
  293.                     else
  294.                     {
  295.                         Current.AppendFormat("{0}={1}\r\n", members[i].Name, objs[i] == null ? NULL : objs[i].ToString());
  296.                     }
  297.                 }
  298.             }
  299.             return Current.ToString() + Postcontent.ToString();
  300.         }
  301.  
  302.         /// <summary>
  303.         /// Gets a section with all its values
  304.         /// </summary>
  305.         /// <param name="Section">Section</param>
  306.         /// <param name="INI">INI lines</param>
  307.         /// <returns>Lines of specified section</returns>
  308.         private string[] GetSection(string Section, string[] INI)
  309.         {
  310.             //0=Not yet in section, 1=in section, 2=section ended
  311.             int sectionState = 0;
  312.             List<string> Lines=new List<string>();
  313.             for (int i = 0; i < INI.Length && sectionState < 2; i++)
  314.             {
  315.                 if (sectionState == 1)
  316.                 {
  317.                     switch (GetType(INI[i]))
  318.                     {
  319.                         case LineType.Property:
  320.                             Lines.Add(INI[i]);
  321.                             break;
  322.                         case LineType.Section:
  323.                             sectionState = 2;
  324.                             break;
  325.                     }
  326.                 }
  327.                 else
  328.                 {
  329.                     sectionState = (INI[i] == string.Format("[{0}]", Section)) ? 1 : 0;
  330.                 }
  331.             }
  332.             return Lines.ToArray();
  333.         }
  334.  
  335.         /// <summary>
  336.         /// Converts multiple ini lines into the array it comes from
  337.         /// </summary>
  338.         /// <param name="Property">property to search for</param>
  339.         /// <param name="Lines">Lines to search</param>
  340.         /// <returns>string array of values</returns>
  341.         private string[] GetArrayLine(string Property, string[] Lines)
  342.         {
  343.             List<ArrayEntry> Props = new List<ArrayEntry>();
  344.  
  345.             //get rid of array indexes in property name
  346.             if (Property.Contains("["))
  347.             {
  348.                 Property = Property.Substring(0, Property.IndexOf('['));
  349.             }
  350.  
  351.             foreach (string Line in Lines)
  352.             {
  353.                 if (GetType(Line) == LineType.Property && Line.StartsWith(Property+"["))
  354.                 {
  355.                     Props.Add(new ArrayEntry(Line));
  356.                 }
  357.             }
  358.             Props.Sort(Sorter);
  359.             return Props.ConvertAll<string>(Extractor).ToArray();
  360.         }
  361.  
  362.         /// <summary>
  363.         /// gets the values from ArrayEntry
  364.         /// </summary>
  365.         /// <param name="E"></param>
  366.         /// <returns></returns>
  367.         private string Extractor(ArrayEntry E)
  368.         {
  369.             return E.Value;
  370.         }
  371.  
  372.         /// <summary>
  373.         /// Sorts array entries
  374.         /// </summary>
  375.         /// <param name="Low">Lower array entry</param>
  376.         /// <param name="High">Upper array entry</param>
  377.         /// <returns>sorting value</returns>
  378.         private int Sorter(ArrayEntry Low,ArrayEntry High)
  379.         {
  380.             return Low.Index - High.Index;
  381.         }
  382.  
  383.         /// <summary>
  384.         /// Splits a line into Name and value
  385.         /// </summary>
  386.         /// <param name="Line">INI Line</param>
  387.         /// <returns>Array with 2 values: Name and value</returns>
  388.         private string[] SplitLine(string Line)
  389.         {
  390.             return new string[]
  391.                 {
  392.                     Line.Substring(0,Line.IndexOf('=')),
  393.                     Line.Substring(Line.IndexOf('=')+1)
  394.                 };
  395.         }
  396.  
  397.         /// <summary>
  398.         /// Extracts the current object type of a name chain
  399.         /// </summary>
  400.         /// <param name="Namechain">Name chain</param>
  401.         /// <returns>object type as string</returns>
  402.         private string extractType(string Namechain)
  403.         {
  404.             string Last = Namechain.Split(',')[Namechain.Split(',').Length - 1];
  405.             if (Last.Contains("["))
  406.             {
  407.                 return Last.Substring(0, Last.IndexOf('['));
  408.             }
  409.             return Last;
  410.         }
  411.  
  412.         /// <summary>
  413.         /// Gets the type of the current line
  414.         /// </summary>
  415.         /// <param name="Line">Line</param>
  416.         /// <returns>Type of line</returns>
  417.         private LineType GetType(string Line)
  418.         {
  419.             if (Line.StartsWith("[") && Line.EndsWith("]"))
  420.             {
  421.                 return LineType.Section;
  422.             }
  423.             if (Line.StartsWith(";") || Line.StartsWith("#"))
  424.             {
  425.                 return LineType.Comment;
  426.             }
  427.             if (Line.Contains("="))
  428.             {
  429.                 return LineType.Property;
  430.             }
  431.             if (string.IsNullOrEmpty(Line.Trim()))
  432.             {
  433.                 return LineType.Empty;
  434.             }
  435.             return LineType.Invalid;
  436.         }
  437.  
  438.         /// <summary>
  439.         /// Checks if a line is of a given type
  440.         /// </summary>
  441.         /// <param name="Line">Line to check</param>
  442.         /// <param name="Type">Type to check against</param>
  443.         /// <returns>true, if types match</returns>
  444.         private bool IsType(string Line, LineType Type)
  445.         {
  446.             return GetType(Line) == Type;
  447.         }
  448.  
  449.         /// <summary>
  450.         /// converts a safe string to a regular string
  451.         /// </summary>
  452.         /// <param name="s">safe string</param>
  453.         /// <param name="INI">INI file for further parsing</param>
  454.         /// <returns>object value</returns>
  455.         private object FromString(string s, string[] INI)
  456.         {
  457.             if (s == NULL)
  458.             {
  459.                 return null;
  460.             }
  461.             if (s.StartsWith(ARRAY))
  462.             {
  463.                 string[] parts = s.Substring(ARRAY.Length).Split('|');
  464.                 object[] obj = new object[parts.Length];
  465.                 for (int i = 0; i < parts.Length; i++)
  466.                 {
  467.                     obj[i] = FromString(parts[i], INI);
  468.                 }
  469.                 return obj;
  470.             }
  471.             if (s.StartsWith("[") && s.EndsWith("]"))
  472.             {
  473.                 return AssembleSection(s.Substring(1, s.Length - 2), INI);
  474.             }
  475.             if (s.StartsWith(B64))
  476.             {
  477.                 return Encoding.UTF8.GetString(Convert.FromBase64String(s.Substring(B64.Length)));
  478.             }
  479.             return s == NULL ? null : s;
  480.         }
  481.  
  482.         /// <summary>
  483.         /// converts an object to a safe string
  484.         /// </summary>
  485.         /// <param name="o">object (primitive type only)</param>
  486.         /// <returns>safe string</returns>
  487.         private string ToString(object o)
  488.         {
  489.             if(o.GetType().FullName!=string.Empty.GetType().FullName && !o.GetType().IsPrimitive)
  490.             {
  491.                 throw new ArgumentException("tried to convert non-primitive type to string");
  492.             }
  493.  
  494.             if (o == null)
  495.             {
  496.                 return NULL;
  497.             }
  498.             bool offend = false;
  499.             string s = o.ToString();
  500.             offend = (s.StartsWith("[") && s.EndsWith("]")) || s.StartsWith(B64) || s == NULL || s.StartsWith(ARRAY);
  501.             for (int i = 0; i < replaceables.Length && !offend; i++)
  502.             {
  503.                 offend = offend || s.Contains(replaceables[i]);
  504.             }
  505.             //convert to base64 if offending content
  506.             if (offend)
  507.             {
  508.                 s = B64 + Convert.ToBase64String(Encoding.UTF8.GetBytes(s), Base64FormattingOptions.None).Replace('=', '_');
  509.             }
  510.             return s;
  511.         }
  512.     }
  513. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement