123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595 |
- //
- // Copyright 2020 Electronic Arts Inc.
- //
- // The Command & Conquer Map Editor and corresponding source code is free
- // software: you can redistribute it and/or modify it under the terms of
- // the GNU General Public License as published by the Free Software Foundation,
- // either version 3 of the License, or (at your option) any later version.
- // The Command & Conquer Map Editor and corresponding source code is distributed
- // in the hope that it will be useful, but with permitted additional restrictions
- // under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
- // distributed with this program. You should have received a copy of the
- // GNU General Public License along with permitted additional restrictions
- // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.Collections.Specialized;
- using System.ComponentModel;
- using System.IO;
- using System.Linq;
- using System.Reflection;
- using System.Text;
- using System.Text.RegularExpressions;
- namespace MobiusEditor.Utility
- {
- static class INIHelpers
- {
- public static readonly Regex SectionRegex = new Regex(@"^\s*\[([^\]]*)\]", RegexOptions.Compiled);
- public static readonly Regex KeyValueRegex = new Regex(@"^\s*(.*?)\s*=([^;]*)", RegexOptions.Compiled);
- public static readonly Regex CommentRegex = new Regex(@"^\s*(#|;)", RegexOptions.Compiled);
- public static readonly Func<INIDiffType, string> DiffPrefix = t =>
- {
- switch (t)
- {
- case INIDiffType.Added:
- return "+";
- case INIDiffType.Removed:
- return "-";
- case INIDiffType.Updated:
- return "@";
- }
- return string.Empty;
- };
- }
- public class INIKeyValueCollection : IEnumerable<(string Key, string Value)>, IEnumerable
- {
- private readonly OrderedDictionary KeyValues;
- public string this[string key]
- {
- get
- {
- if (!KeyValues.Contains(key))
- {
- throw new KeyNotFoundException(key);
- }
- return KeyValues[key] as string;
- }
- set
- {
- if (key == null)
- {
- throw new ArgumentNullException("key");
- }
- KeyValues[key] = value;
- }
- }
- public INIKeyValueCollection()
- {
- KeyValues = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
- }
- public int Count => KeyValues.Count;
- public bool Contains(string key) => KeyValues.Contains(key);
- public T Get<T>(string key) where T : struct
- {
- var converter = TypeDescriptor.GetConverter(typeof(T));
- return (T)converter.ConvertFromString(this[key]);
- }
- public void Set<T>(string key, T value) where T : struct
- {
- var converter = TypeDescriptor.GetConverter(typeof(T));
- this[key] = converter.ConvertToString(value);
- }
- public bool Remove(string key)
- {
- if (!KeyValues.Contains(key))
- {
- return false;
- }
- KeyValues.Remove(key);
- return true;
- }
- public IEnumerator<(string Key, string Value)> GetEnumerator()
- {
- foreach (DictionaryEntry entry in KeyValues)
- {
- yield return (entry.Key as string, entry.Value as string);
- }
- }
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
- }
- public class INISection : IEnumerable<(string Key, string Value)>, IEnumerable
- {
- public readonly INIKeyValueCollection Keys;
- public string Name { get; private set; }
- public string this[string key] { get => Keys[key]; set => Keys[key] = value; }
- public bool Empty => Keys.Count == 0;
- public INISection(string name)
- {
- Keys = new INIKeyValueCollection();
- Name = name;
- }
- public void Parse(TextReader reader)
- {
- while (true)
- {
- var line = reader.ReadLine();
- if (line == null)
- {
- break;
- }
- var m = INIHelpers.KeyValueRegex.Match(line);
- if (m.Success)
- {
- Keys[m.Groups[1].Value] = m.Groups[2].Value;
- }
- }
- }
- public void Parse(string iniText)
- {
- using (var reader = new StringReader(iniText))
- {
- Parse(reader);
- }
- }
- public IEnumerator<(string Key, string Value)> GetEnumerator()
- {
- return Keys.GetEnumerator();
- }
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
- public override string ToString()
- {
- var lines = new List<string>(Keys.Count);
- foreach (var item in Keys)
- {
- lines.Add(string.Format("{0}={1}", item.Key, item.Value));
- }
- return string.Join(Environment.NewLine, lines);
- }
- }
- public class INISectionCollection : IEnumerable<INISection>, IEnumerable
- {
- private readonly OrderedDictionary Sections;
- public INISection this[string name] => Sections.Contains(name) ? (Sections[name] as INISection) : null;
- public INISectionCollection()
- {
- Sections = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
- }
- public int Count => Sections.Count;
- public bool Contains(string section) => Sections.Contains(section);
- public INISection Add(string name)
- {
- if (!Sections.Contains(name))
- {
- var section = new INISection(name);
- Sections[name] = section;
- }
- return this[name];
- }
- public bool Add(INISection section)
- {
- if ((section == null) || Sections.Contains(section.Name))
- {
- return false;
- }
- Sections[section.Name] = section;
- return true;
- }
- public void AddRange(IEnumerable<INISection> sections)
- {
- foreach (var section in sections)
- {
- Add(section);
- }
- }
- public bool Remove(string name)
- {
- if (!Sections.Contains(name))
- {
- return false;
- }
- Sections.Remove(name);
- return true;
- }
- public INISection Extract(string name)
- {
- if (!Sections.Contains(name))
- {
- return null;
- }
- var section = this[name];
- Sections.Remove(name);
- return section;
- }
- public IEnumerator<INISection> GetEnumerator()
- {
- foreach (DictionaryEntry entry in Sections)
- {
- yield return entry.Value as INISection;
- }
- }
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
- }
- public partial class INI : IEnumerable<INISection>, IEnumerable
- {
- public readonly INISectionCollection Sections;
- public INISection this[string name] { get => Sections[name]; }
- public INI()
- {
- Sections = new INISectionCollection();
- }
- public void Parse(TextReader reader)
- {
- INISection currentSection = null;
- while (true)
- {
- var line = reader.ReadLine();
- if (line == null)
- {
- break;
- }
- var m = INIHelpers.SectionRegex.Match(line);
- if (m.Success)
- {
- currentSection = Sections.Add(m.Groups[1].Value);
- }
- if (currentSection != null)
- {
- if (INIHelpers.CommentRegex.Match(line).Success)
- {
- continue;
- }
- currentSection.Parse(line);
- }
- }
- }
- public void Parse(string iniText)
- {
- using (var reader = new StringReader(iniText))
- {
- Parse(reader);
- }
- }
- public IEnumerator<INISection> GetEnumerator()
- {
- foreach (var section in Sections)
- {
- yield return section;
- }
- }
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
- public override string ToString()
- {
- var sections = new List<string>(Sections.Count);
- foreach (var item in Sections)
- {
- var lines = new List<string>
- {
- string.Format("[{0}]", item.Name)
- };
- if (!item.Empty)
- {
- lines.Add(item.ToString());
- }
- sections.Add(string.Join(Environment.NewLine, lines));
- }
- return string.Join(Environment.NewLine + Environment.NewLine, sections) + Environment.NewLine;
- }
- }
- [Flags]
- public enum INIDiffType
- {
- None = 0,
- Added = 1,
- Removed = 2,
- Updated = 4,
- AddedOrUpdated = 5
- }
- public class INISectionDiff : IEnumerable<string>, IEnumerable
- {
- public readonly INIDiffType Type;
- private readonly Dictionary<string, INIDiffType> keyDiff;
- public INIDiffType this[string key]
- {
- get
- {
- INIDiffType diffType;
- if (!keyDiff.TryGetValue(key, out diffType))
- {
- return INIDiffType.None;
- }
- return diffType;
- }
- }
- private INISectionDiff()
- {
- keyDiff = new Dictionary<string, INIDiffType>();
- Type = INIDiffType.None;
- }
- internal INISectionDiff(INIDiffType type, INISection section)
- : this()
- {
- foreach (var keyValue in section.Keys)
- {
- keyDiff[keyValue.Key] = type;
- }
- Type = type;
- }
- internal INISectionDiff(INISection leftSection, INISection rightSection)
- : this(INIDiffType.Removed, leftSection)
- {
- foreach (var keyValue in rightSection.Keys)
- {
- var key = keyValue.Key;
- if (keyDiff.ContainsKey(key))
- {
- if (leftSection[key] == rightSection[key])
- {
- keyDiff.Remove(key);
- }
- else
- {
- keyDiff[key] = INIDiffType.Updated;
- Type = INIDiffType.Updated;
- }
- }
- else
- {
- keyDiff[key] = INIDiffType.Added;
- Type = INIDiffType.Updated;
- }
- }
- Type = (keyDiff.Count > 0) ? INIDiffType.Updated : INIDiffType.None;
- }
- public IEnumerator<string> GetEnumerator()
- {
- return keyDiff.Keys.GetEnumerator();
- }
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
- public override string ToString()
- {
- var sb = new StringBuilder();
- foreach (var item in keyDiff)
- {
- sb.AppendLine(string.Format("{0} {1}", INIHelpers.DiffPrefix(item.Value), item.Key));
- }
- return sb.ToString();
- }
- }
- public class INIDiff : IEnumerable<string>, IEnumerable
- {
- private readonly Dictionary<string, INISectionDiff> sectionDiffs;
- public INISectionDiff this[string key]
- {
- get
- {
- if (!sectionDiffs.TryGetValue(key, out INISectionDiff sectionDiff))
- {
- return null;
- }
- return sectionDiff;
- }
- }
- private INIDiff()
- {
- sectionDiffs = new Dictionary<string, INISectionDiff>(StringComparer.OrdinalIgnoreCase);
- }
- public INIDiff(INI leftIni, INI rightIni)
- : this()
- {
- foreach (var leftSection in leftIni)
- {
- sectionDiffs[leftSection.Name] = rightIni.Sections.Contains(leftSection.Name) ?
- new INISectionDiff(leftSection, rightIni[leftSection.Name]) :
- new INISectionDiff(INIDiffType.Removed, leftSection);
- }
- foreach (var rightSection in rightIni)
- {
- if (!leftIni.Sections.Contains(rightSection.Name))
- {
- sectionDiffs[rightSection.Name] = new INISectionDiff(INIDiffType.Added, rightSection);
- }
- }
- sectionDiffs = sectionDiffs.Where(x => x.Value.Type != INIDiffType.None).ToDictionary(x => x.Key, x => x.Value);
- }
- public bool Contains(string key) => sectionDiffs.ContainsKey(key);
- public IEnumerator<string> GetEnumerator()
- {
- return sectionDiffs.Keys.GetEnumerator();
- }
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
- public override string ToString()
- {
- var sb = new StringBuilder();
- foreach (var item in sectionDiffs)
- {
- sb.AppendLine(string.Format("{0} {1}", INIHelpers.DiffPrefix(item.Value.Type), item.Key));
- using (var reader = new StringReader(item.Value.ToString()))
- {
- while (true)
- {
- var line = reader.ReadLine();
- if (line == null)
- {
- break;
- }
- sb.AppendLine(string.Format("\t{0}", line));
- }
- }
- }
- return sb.ToString();
- }
- }
- [AttributeUsage(AttributeTargets.Property)]
- public class NonSerializedINIKeyAttribute : Attribute
- {
- }
- public partial class INI
- {
- public static void ParseSection<T>(ITypeDescriptorContext context, INISection section, T data)
- {
- var propertyDescriptors = TypeDescriptor.GetProperties(data);
- var properties = data.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.GetSetMethod() != null);
- foreach (var property in properties)
- {
- if (property.GetCustomAttribute<NonSerializedINIKeyAttribute>() != null)
- {
- continue;
- }
- if (section.Keys.Contains(property.Name))
- {
- var converter = propertyDescriptors.Find(property.Name, false)?.Converter ?? TypeDescriptor.GetConverter(property.PropertyType);
- if (converter.CanConvertFrom(context, typeof(string)))
- {
- try
- {
- property.SetValue(data, converter.ConvertFromString(context, section[property.Name]));
- }
- catch (FormatException)
- {
- if (property.PropertyType == typeof(bool))
- {
- var value = section[property.Name].ToLower();
- if (value == "no")
- {
- property.SetValue(data, false);
- }
- else if (value == "yes")
- {
- property.SetValue(data, true);
- }
- else
- {
- throw;
- }
- }
- else
- {
- throw;
- }
- }
- }
- }
- }
- }
- public static void WriteSection<T>(ITypeDescriptorContext context, INISection section, T data)
- {
- var propertyDescriptors = TypeDescriptor.GetProperties(data);
- var properties = data.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.GetGetMethod() != null);
- foreach (var property in properties)
- {
- if (property.GetCustomAttribute<NonSerializedINIKeyAttribute>() != null)
- {
- continue;
- }
- var value = property.GetValue(data);
- if (property.PropertyType.IsValueType || (value != null))
- {
- var converter = propertyDescriptors.Find(property.Name, false)?.Converter ?? TypeDescriptor.GetConverter(property.PropertyType);
- if (converter.CanConvertTo(context, typeof(string)))
- {
- section[property.Name] = converter.ConvertToString(context, value);
- }
- }
- }
- }
- public static void ParseSection<T>(INISection section, T data) => ParseSection(null, section, data);
- public static void WriteSection<T>(INISection section, T data) => WriteSection(null, section, data);
- }
- }
|