CnC_Remastered_Collection

Command and Conquer: Red Alert
Log | Files | Refs | README | LICENSE

INI.cs (18332B)


      1 //
      2 // Copyright 2020 Electronic Arts Inc.
      3 //
      4 // The Command & Conquer Map Editor and corresponding source code is free 
      5 // software: you can redistribute it and/or modify it under the terms of 
      6 // the GNU General Public License as published by the Free Software Foundation, 
      7 // either version 3 of the License, or (at your option) any later version.
      8 
      9 // The Command & Conquer Map Editor and corresponding source code is distributed 
     10 // in the hope that it will be useful, but with permitted additional restrictions 
     11 // under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT 
     12 // distributed with this program. You should have received a copy of the 
     13 // GNU General Public License along with permitted additional restrictions 
     14 // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
     15 using System;
     16 using System.Collections;
     17 using System.Collections.Generic;
     18 using System.Collections.Specialized;
     19 using System.ComponentModel;
     20 using System.IO;
     21 using System.Linq;
     22 using System.Reflection;
     23 using System.Text;
     24 using System.Text.RegularExpressions;
     25 
     26 namespace MobiusEditor.Utility
     27 {
     28     static class INIHelpers
     29     {
     30         public static readonly Regex SectionRegex = new Regex(@"^\s*\[([^\]]*)\]", RegexOptions.Compiled);
     31         public static readonly Regex KeyValueRegex = new Regex(@"^\s*(.*?)\s*=([^;]*)", RegexOptions.Compiled);
     32         public static readonly Regex CommentRegex = new Regex(@"^\s*(#|;)", RegexOptions.Compiled);
     33 
     34         public static readonly Func<INIDiffType, string> DiffPrefix = t =>
     35         {
     36             switch (t)
     37             {
     38                 case INIDiffType.Added:
     39                     return "+";
     40                 case INIDiffType.Removed:
     41                     return "-";
     42                 case INIDiffType.Updated:
     43                     return "@";
     44             }
     45             return string.Empty;
     46         };
     47     }
     48 
     49     public class INIKeyValueCollection : IEnumerable<(string Key, string Value)>, IEnumerable
     50     {
     51         private readonly OrderedDictionary KeyValues;
     52 
     53         public string this[string key]
     54         {
     55             get
     56             {
     57                 if (!KeyValues.Contains(key))
     58                 {
     59                     throw new KeyNotFoundException(key);
     60                 }
     61                 return KeyValues[key] as string;
     62             }
     63             set
     64             {
     65                 if (key == null)
     66                 {
     67                     throw new ArgumentNullException("key");
     68                 }
     69                 KeyValues[key] = value;
     70             }
     71         }
     72 
     73         public INIKeyValueCollection()
     74         {
     75             KeyValues = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
     76         }
     77 
     78         public int Count => KeyValues.Count;
     79 
     80         public bool Contains(string key) => KeyValues.Contains(key);
     81 
     82         public T Get<T>(string key) where T : struct
     83         {
     84             var converter = TypeDescriptor.GetConverter(typeof(T));
     85             return (T)converter.ConvertFromString(this[key]);
     86         }
     87 
     88         public void Set<T>(string key, T value) where T : struct
     89         {
     90             var converter = TypeDescriptor.GetConverter(typeof(T));
     91             this[key] = converter.ConvertToString(value);
     92         }
     93 
     94         public bool Remove(string key)
     95         {
     96             if (!KeyValues.Contains(key))
     97             {
     98                 return false;
     99             }
    100             KeyValues.Remove(key);
    101             return true;
    102         }
    103 
    104         public IEnumerator<(string Key, string Value)> GetEnumerator()
    105         {
    106             foreach (DictionaryEntry entry in KeyValues)
    107             {
    108                 yield return (entry.Key as string, entry.Value as string);
    109             }
    110         }
    111 
    112         IEnumerator IEnumerable.GetEnumerator()
    113         {
    114             return GetEnumerator();
    115         }
    116     }
    117 
    118     public class INISection : IEnumerable<(string Key, string Value)>, IEnumerable
    119     {
    120         public readonly INIKeyValueCollection Keys;
    121 
    122         public string Name { get; private set; }
    123 
    124         public string this[string key] { get => Keys[key]; set => Keys[key] = value; }
    125 
    126         public bool Empty => Keys.Count == 0;
    127 
    128         public INISection(string name)
    129         {
    130             Keys = new INIKeyValueCollection();
    131             Name = name;
    132         }
    133 
    134         public void Parse(TextReader reader)
    135         {
    136             while (true)
    137             {
    138                 var line = reader.ReadLine();
    139                 if (line == null)
    140                 {
    141                     break;
    142                 }
    143 
    144                 var m = INIHelpers.KeyValueRegex.Match(line);
    145                 if (m.Success)
    146                 {
    147                     Keys[m.Groups[1].Value] = m.Groups[2].Value;
    148                 }
    149             }
    150         }
    151 
    152         public void Parse(string iniText)
    153         {
    154             using (var reader = new StringReader(iniText))
    155             {
    156                 Parse(reader);
    157             }
    158         }
    159 
    160         public IEnumerator<(string Key, string Value)> GetEnumerator()
    161         {
    162             return Keys.GetEnumerator();
    163         }
    164 
    165         IEnumerator IEnumerable.GetEnumerator()
    166         {
    167             return GetEnumerator();
    168         }
    169 
    170         public override string ToString()
    171         {
    172             var lines = new List<string>(Keys.Count);
    173             foreach (var item in Keys)
    174             {
    175                 lines.Add(string.Format("{0}={1}", item.Key, item.Value));
    176             }
    177             return string.Join(Environment.NewLine, lines);
    178         }
    179     }
    180 
    181     public class INISectionCollection : IEnumerable<INISection>, IEnumerable
    182     {
    183         private readonly OrderedDictionary Sections;
    184 
    185         public INISection this[string name] => Sections.Contains(name) ? (Sections[name] as INISection) : null;
    186 
    187         public INISectionCollection()
    188         {
    189             Sections = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
    190         }
    191 
    192         public int Count => Sections.Count;
    193 
    194         public bool Contains(string section) => Sections.Contains(section);
    195 
    196         public INISection Add(string name)
    197         {
    198             if (!Sections.Contains(name))
    199             {
    200                 var section = new INISection(name);
    201                 Sections[name] = section;
    202             }
    203             return this[name];
    204         }
    205 
    206         public bool Add(INISection section)
    207         {
    208             if ((section == null) || Sections.Contains(section.Name))
    209             {
    210                 return false;
    211             }
    212             Sections[section.Name] = section;
    213             return true;
    214         }
    215 
    216         public void AddRange(IEnumerable<INISection> sections)
    217         {
    218             foreach (var section in sections)
    219             {
    220                 Add(section);
    221             }
    222         }
    223 
    224         public bool Remove(string name)
    225         {
    226             if (!Sections.Contains(name))
    227             {
    228                 return false;
    229             }
    230             Sections.Remove(name);
    231             return true;
    232         }
    233 
    234         public INISection Extract(string name)
    235         {
    236             if (!Sections.Contains(name))
    237             {
    238                 return null;
    239             }
    240             var section = this[name];
    241             Sections.Remove(name);
    242             return section;
    243         }
    244 
    245         public IEnumerator<INISection> GetEnumerator()
    246         {
    247             foreach (DictionaryEntry entry in Sections)
    248             {
    249                 yield return entry.Value as INISection;
    250             }
    251         }
    252 
    253         IEnumerator IEnumerable.GetEnumerator()
    254         {
    255             return GetEnumerator();
    256         }
    257     }
    258 
    259     public partial class INI : IEnumerable<INISection>, IEnumerable
    260     {
    261         public readonly INISectionCollection Sections;
    262 
    263         public INISection this[string name] { get => Sections[name]; }
    264 
    265         public INI()
    266         {
    267             Sections = new INISectionCollection();
    268         }
    269 
    270         public void Parse(TextReader reader)
    271         {
    272             INISection currentSection = null;
    273 
    274             while (true)
    275             {
    276                 var line = reader.ReadLine();
    277                 if (line == null)
    278                 {
    279                     break;
    280                 }
    281 
    282                 var m = INIHelpers.SectionRegex.Match(line);
    283                 if (m.Success)
    284                 {
    285                     currentSection = Sections.Add(m.Groups[1].Value);
    286                 }
    287 
    288                 if (currentSection != null)
    289                 {
    290                     if (INIHelpers.CommentRegex.Match(line).Success)
    291                     {
    292                         continue;
    293                     }
    294 
    295                     currentSection.Parse(line);
    296                 }
    297             }
    298         }
    299 
    300         public void Parse(string iniText)
    301         {
    302             using (var reader = new StringReader(iniText))
    303             {
    304                 Parse(reader);
    305             }
    306         }
    307 
    308         public IEnumerator<INISection> GetEnumerator()
    309         {
    310             foreach (var section in Sections)
    311             {
    312                 yield return section;
    313             }
    314         }
    315 
    316         IEnumerator IEnumerable.GetEnumerator()
    317         {
    318             return GetEnumerator();
    319         }
    320 
    321         public override string ToString()
    322         {
    323             var sections = new List<string>(Sections.Count);
    324             foreach (var item in Sections)
    325             {
    326                 var lines = new List<string>
    327                 {
    328                     string.Format("[{0}]", item.Name)
    329                 };
    330                 if (!item.Empty)
    331                 {
    332                     lines.Add(item.ToString());
    333                 }
    334                 sections.Add(string.Join(Environment.NewLine, lines));
    335             }
    336             return string.Join(Environment.NewLine + Environment.NewLine, sections) + Environment.NewLine;
    337         }
    338     }
    339 
    340     [Flags]
    341     public enum INIDiffType
    342     {
    343         None = 0,
    344         Added = 1,
    345         Removed = 2,
    346         Updated = 4,
    347         AddedOrUpdated = 5
    348     }
    349 
    350     public class INISectionDiff : IEnumerable<string>, IEnumerable
    351     {
    352         public readonly INIDiffType Type;
    353 
    354         private readonly Dictionary<string, INIDiffType> keyDiff;
    355 
    356         public INIDiffType this[string key]
    357         {
    358             get
    359             {
    360                 INIDiffType diffType;
    361                 if (!keyDiff.TryGetValue(key, out diffType))
    362                 {
    363                     return INIDiffType.None;
    364                 }
    365                 return diffType;
    366             }
    367         }
    368 
    369         private INISectionDiff()
    370         {
    371             keyDiff = new Dictionary<string, INIDiffType>();
    372             Type = INIDiffType.None;
    373         }
    374 
    375         internal INISectionDiff(INIDiffType type, INISection section)
    376             : this()
    377         {
    378             foreach (var keyValue in section.Keys)
    379             {
    380                 keyDiff[keyValue.Key] = type;
    381             }
    382 
    383             Type = type;
    384         }
    385 
    386         internal INISectionDiff(INISection leftSection, INISection rightSection)
    387             : this(INIDiffType.Removed, leftSection)
    388         {
    389             foreach (var keyValue in rightSection.Keys)
    390             {
    391                 var key = keyValue.Key;
    392                 if (keyDiff.ContainsKey(key))
    393                 {
    394                     if (leftSection[key] == rightSection[key])
    395                     {
    396                         keyDiff.Remove(key);
    397                     }
    398                     else
    399                     {
    400                         keyDiff[key] = INIDiffType.Updated;
    401                         Type = INIDiffType.Updated;
    402                     }
    403                 }
    404                 else
    405                 {
    406                     keyDiff[key] = INIDiffType.Added;
    407                     Type = INIDiffType.Updated;
    408                 }
    409             }
    410 
    411             Type = (keyDiff.Count > 0) ? INIDiffType.Updated : INIDiffType.None;
    412         }
    413 
    414         public IEnumerator<string> GetEnumerator()
    415         {
    416             return keyDiff.Keys.GetEnumerator();
    417         }
    418 
    419         IEnumerator IEnumerable.GetEnumerator()
    420         {
    421             return GetEnumerator();
    422         }
    423 
    424         public override string ToString()
    425         {
    426             var sb = new StringBuilder();
    427             foreach (var item in keyDiff)
    428             {
    429                 sb.AppendLine(string.Format("{0} {1}", INIHelpers.DiffPrefix(item.Value), item.Key));
    430             }
    431             return sb.ToString();
    432         }
    433     }
    434 
    435     public class INIDiff : IEnumerable<string>, IEnumerable
    436     {
    437         private readonly Dictionary<string, INISectionDiff> sectionDiffs;
    438 
    439         public INISectionDiff this[string key]
    440         {
    441             get
    442             {
    443                 if (!sectionDiffs.TryGetValue(key, out INISectionDiff sectionDiff))
    444                 {
    445                     return null;
    446                 }
    447                 return sectionDiff;
    448             }
    449         }
    450 
    451         private INIDiff()
    452         {
    453             sectionDiffs = new Dictionary<string, INISectionDiff>(StringComparer.OrdinalIgnoreCase);
    454         }
    455 
    456         public INIDiff(INI leftIni, INI rightIni)
    457             : this()
    458         {
    459             foreach (var leftSection in leftIni)
    460             {
    461                 sectionDiffs[leftSection.Name] = rightIni.Sections.Contains(leftSection.Name) ?
    462                     new INISectionDiff(leftSection, rightIni[leftSection.Name]) :
    463                     new INISectionDiff(INIDiffType.Removed, leftSection);
    464             }
    465 
    466             foreach (var rightSection in rightIni)
    467             {
    468                 if (!leftIni.Sections.Contains(rightSection.Name))
    469                 {
    470                     sectionDiffs[rightSection.Name] = new INISectionDiff(INIDiffType.Added, rightSection);
    471                 }
    472             }
    473 
    474             sectionDiffs = sectionDiffs.Where(x => x.Value.Type != INIDiffType.None).ToDictionary(x => x.Key, x => x.Value);
    475         }
    476 
    477         public bool Contains(string key) => sectionDiffs.ContainsKey(key);
    478 
    479         public IEnumerator<string> GetEnumerator()
    480         {
    481             return sectionDiffs.Keys.GetEnumerator();
    482         }
    483 
    484         IEnumerator IEnumerable.GetEnumerator()
    485         {
    486             return GetEnumerator();
    487         }
    488 
    489         public override string ToString()
    490         {
    491             var sb = new StringBuilder();
    492             foreach (var item in sectionDiffs)
    493             {
    494                 sb.AppendLine(string.Format("{0} {1}", INIHelpers.DiffPrefix(item.Value.Type), item.Key));
    495                 using (var reader = new StringReader(item.Value.ToString()))
    496                 {
    497                     while (true)
    498                     {
    499                         var line = reader.ReadLine();
    500                         if (line == null)
    501                         {
    502                             break;
    503                         }
    504 
    505                         sb.AppendLine(string.Format("\t{0}", line));
    506                     }
    507                 }
    508             }
    509             return sb.ToString();
    510         }
    511     }
    512 
    513     [AttributeUsage(AttributeTargets.Property)]
    514     public class NonSerializedINIKeyAttribute : Attribute
    515     {
    516     }
    517 
    518     public partial class INI
    519     {
    520         public static void ParseSection<T>(ITypeDescriptorContext context, INISection section, T data)
    521         {
    522             var propertyDescriptors = TypeDescriptor.GetProperties(data);
    523             var properties = data.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.GetSetMethod() != null);
    524             foreach (var property in properties)
    525             {
    526                 if (property.GetCustomAttribute<NonSerializedINIKeyAttribute>() != null)
    527                 {
    528                     continue;
    529                 }
    530 
    531                 if (section.Keys.Contains(property.Name))
    532                 {
    533                     var converter = propertyDescriptors.Find(property.Name, false)?.Converter ?? TypeDescriptor.GetConverter(property.PropertyType);
    534                     if (converter.CanConvertFrom(context, typeof(string)))
    535                     {
    536                         try
    537                         {
    538                             property.SetValue(data, converter.ConvertFromString(context, section[property.Name]));
    539                         }
    540                         catch (FormatException)
    541                         {
    542                             if (property.PropertyType == typeof(bool))
    543                             {
    544                                 var value = section[property.Name].ToLower();
    545                                 if (value == "no")
    546                                 {
    547                                     property.SetValue(data, false);
    548                                 }
    549                                 else if (value == "yes")
    550                                 {
    551                                     property.SetValue(data, true);
    552                                 }
    553                                 else
    554                                 {
    555                                     throw;
    556                                 }
    557                             }
    558                             else
    559                             {
    560                                 throw;
    561                             }
    562                         }
    563                     }
    564                 }
    565             }
    566         }
    567 
    568         public static void WriteSection<T>(ITypeDescriptorContext context, INISection section, T data)
    569         {
    570             var propertyDescriptors = TypeDescriptor.GetProperties(data);
    571             var properties = data.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.GetGetMethod() != null);
    572             foreach (var property in properties)
    573             {
    574                 if (property.GetCustomAttribute<NonSerializedINIKeyAttribute>() != null)
    575                 {
    576                     continue;
    577                 }
    578 
    579                 var value = property.GetValue(data);
    580                 if (property.PropertyType.IsValueType || (value != null))
    581                 {
    582                     var converter = propertyDescriptors.Find(property.Name, false)?.Converter ?? TypeDescriptor.GetConverter(property.PropertyType);
    583                     if (converter.CanConvertTo(context, typeof(string)))
    584                     {
    585                         section[property.Name] = converter.ConvertToString(context, value);
    586                     }
    587                 }
    588             }
    589         }
    590 
    591         public static void ParseSection<T>(INISection section, T data) => ParseSection(null, section, data);
    592 
    593         public static void WriteSection<T>(INISection section, T data) => WriteSection(null, section, data);
    594     }
    595 }