Improve display and function of character replacement

This commit is contained in:
Michael Bucari-Tovo 2022-06-21 23:39:24 -06:00
parent 0cb18f9e1a
commit b698697256
5 changed files with 272 additions and 249 deletions

View file

@ -2,43 +2,177 @@
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace FileManager
{
public class Replacement
{
[JsonIgnore]
public bool Mandatory { get; set; }
[JsonProperty]
public char CharacterToReplace { get; init; }
[JsonProperty]
public string ReplacementString { get; set; }
[JsonProperty]
public string Description { get; set; }
public Replacement Clone() => new()
{
Mandatory = Mandatory,
CharacterToReplace = CharacterToReplace,
ReplacementString = ReplacementString,
Description = Description
};
public const int FIXED_COUNT = 4;
internal const char QUOTE_MARK = '"';
internal const string DEFAULT_DESCRIPTION = "Any other invalid characters";
internal const string OPEN_QUOTE_DESCRIPTION = "Open Quote";
internal const string CLOSE_QUOTE_DESCRIPTION = "Close Quote";
internal const string OTHER_QUOTE_DESCRIPTION = "Other Quote";
[JsonIgnore] public bool Mandatory { get; internal set; }
[JsonProperty] public char CharacterToReplace { get; private set; }
[JsonProperty] public string ReplacementString { get; private set; }
[JsonProperty] public string Description { get; private set; }
public override string ToString() => $"{CharacterToReplace} → {ReplacementString} ({Description})";
public static Replacement Colon(string replacement) => new Replacement { CharacterToReplace = ':', Description = "Colon", ReplacementString = replacement};
public static Replacement Asterisk(string replacement) => new Replacement { CharacterToReplace = '*', Description = "Asterisk", ReplacementString = replacement };
public static Replacement QuestionMark(string replacement) => new Replacement { CharacterToReplace = '?', Description = "Question Mark", ReplacementString = replacement };
public static Replacement OpenAngleBracket(string replacement) => new Replacement { CharacterToReplace = '<', Description = "Open Angle Bracket", ReplacementString = replacement };
public static Replacement CloseAngleBracket(string replacement) => new Replacement { CharacterToReplace = '>', Description = "Close Angle Bracket", ReplacementString = replacement };
public static Replacement OpenQuote(string replacement) => new Replacement { CharacterToReplace = '"', Description = "Open Quote", ReplacementString = replacement };
public static Replacement CloseQuote(string replacement) => new Replacement { CharacterToReplace = '"', Description = "Close Quote", ReplacementString = replacement };
public static Replacement OtherQuote(string replacement) => new Replacement { CharacterToReplace = '"', Description = "Other Quote", ReplacementString = replacement };
public static Replacement Pipe(string replacement) => new Replacement { CharacterToReplace = '|', Description = "Vertical Line", ReplacementString = replacement };
public static Replacement OtherInvalid(string replacement) => new Replacement { CharacterToReplace = default, Description = "Any other invalid characters", ReplacementString = replacement };
public Replacement(char charToReplace, string replacementString, string description)
{
CharacterToReplace = charToReplace;
ReplacementString = replacementString;
Description = description;
}
private Replacement(char charToReplace, string replacementString, string description, bool mandatory)
: this(charToReplace, replacementString, description)
{
Mandatory = mandatory;
}
public void Update(char charToReplace, string replacementString, string description)
{
ReplacementString = replacementString;
if (!Mandatory)
{
CharacterToReplace = charToReplace;
Description = description;
}
}
public static Replacement OtherInvalid(string replacement) => new(default, replacement, DEFAULT_DESCRIPTION, true);
public static Replacement OpenQuote(string replacement) => new('"', replacement, OPEN_QUOTE_DESCRIPTION, true);
public static Replacement CloseQuote(string replacement) => new('"', replacement, CLOSE_QUOTE_DESCRIPTION, true);
public static Replacement OtherQuote(string replacement) => new('"', replacement, OTHER_QUOTE_DESCRIPTION, true);
public static Replacement Colon(string replacement) => new(':', replacement, "Colon");
public static Replacement Asterisk(string replacement) => new('*', replacement, "Asterisk");
public static Replacement QuestionMark(string replacement) => new('?', replacement, "Question Mark");
public static Replacement OpenAngleBracket(string replacement) => new('<', replacement, "Open Angle Bracket");
public static Replacement CloseAngleBracket(string replacement) => new('>', replacement, "Close Angle Bracket");
public static Replacement Pipe(string replacement) => new('|', replacement, "Vertical Line");
}
[JsonConverter(typeof(ReplacementCharactersConverter))]
public class ReplacementCharacters
{
public static readonly ReplacementCharacters Default = new()
{
Replacements = new List<Replacement>()
{
Replacement.OtherInvalid("_"),
Replacement.OpenQuote("“"),
Replacement.CloseQuote("”"),
Replacement.OtherQuote(""),
Replacement.OpenAngleBracket(""),
Replacement.CloseAngleBracket(""),
Replacement.Colon(""),
Replacement.Asterisk("✱"),
Replacement.QuestionMark(""),
Replacement.Pipe("⏐"),
}
};
public static readonly ReplacementCharacters LoFiDefault = new()
{
Replacements = new List<Replacement>()
{
Replacement.OtherInvalid("_"),
Replacement.OpenQuote("'"),
Replacement.CloseQuote("'"),
Replacement.OtherQuote("'"),
Replacement.OpenAngleBracket("{"),
Replacement.CloseAngleBracket("}"),
Replacement.Colon("-"),
Replacement.Asterisk(""),
Replacement.QuestionMark(""),
}
};
public static readonly ReplacementCharacters Minimum = new()
{
Replacements = new List<Replacement>()
{
Replacement.OtherInvalid("_"),
Replacement.OpenQuote("_"),
Replacement.CloseQuote("_"),
Replacement.OtherQuote("_"),
}
};
private static readonly char[] invalidChars = Path.GetInvalidPathChars().Union(new[] {
'*', '?', ':',
// these are weird. If you run Path.GetInvalidPathChars() in Visual Studio's "C# Interactive", then these characters are included.
// In live code, Path.GetInvalidPathChars() does not include them
'"', '<', '>'
}).ToArray();
public IReadOnlyList<Replacement> Replacements { get; init; }
private string DefaultReplacement => Replacements[0].ReplacementString;
private string OpenQuote => Replacements[1].ReplacementString;
private string CloseQuote => Replacements[2].ReplacementString;
private string OtherQuote => Replacements[3].ReplacementString;
private string GetReplacement(char toReplace, char preceding, char succeding)
{
if (toReplace == Replacement.QUOTE_MARK)
{
if (
preceding != default
&& !char.IsLetter(preceding)
&& !char.IsNumber(preceding)
&& (char.IsLetter(succeding) || char.IsNumber(succeding))
)
return OpenQuote;
else if (
succeding != default
&& !char.IsLetter(succeding)
&& !char.IsNumber(succeding)
&& (char.IsLetter(preceding) || char.IsNumber(preceding))
)
return CloseQuote;
else
return OtherQuote;
}
for (int i = Replacement.FIXED_COUNT; i < Replacements.Count; i++)
{
var r = Replacements[i];
if (r.CharacterToReplace == toReplace)
return r.ReplacementString;
}
return DefaultReplacement;
}
public static bool ContainsInvalid(string path)
=> path.Any(c => invalidChars.Contains(c));
public string ReplaceInvalidChars(string pathStr)
{
// replace all colons except within the first 2 chars
var builder = new System.Text.StringBuilder();
for (var i = 0; i < pathStr.Length; i++)
{
var c = pathStr[i];
if (!invalidChars.Contains(c) || (i <= 2 && Path.IsPathRooted(pathStr)))
builder.Append(c);
else
{
char preceding = i > 0 ? pathStr[i - 1] : default;
char succeeding = i < pathStr.Length - 1 ? pathStr[i + 1] : default;
builder.Append(GetReplacement(c, preceding, succeeding));
}
}
return builder.ToString();
}
}
#region JSON Converter
internal class ReplacementCharactersConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
@ -51,22 +185,21 @@ namespace FileManager
var dict = replaceArr
.ToObject<Replacement[]>().ToList();
//Add any missing defaults and ensure they are in the expected order.
for (int i = 0; i < ReplacementCharacters.Default.Replacements.Count; i++)
//Ensure that the first 4 replacements are for the expected chars and that all replacement strings are valid.
//If not, reset to default.
if (dict.Count < Replacement.FIXED_COUNT ||
dict[0].CharacterToReplace != default || dict[0].Description != Replacement.DEFAULT_DESCRIPTION ||
dict[1].CharacterToReplace != Replacement.QUOTE_MARK || dict[1].Description != Replacement.OPEN_QUOTE_DESCRIPTION ||
dict[2].CharacterToReplace != Replacement.QUOTE_MARK || dict[2].Description != Replacement.CLOSE_QUOTE_DESCRIPTION ||
dict[3].CharacterToReplace != Replacement.QUOTE_MARK || dict[3].Description != Replacement.OTHER_QUOTE_DESCRIPTION ||
dict.Any(r => ReplacementCharacters.ContainsInvalid(r.ReplacementString))
)
{
var rep = ReplacementCharacters.Default.Replacements[i].Clone();
if (i < dict.Count)
{
var replacementStr = dict[i].ReplacementString;
dict[i] = rep;
dict[i].ReplacementString = replacementStr;
}
else
{
dict.Insert(i, rep);
}
dict = ReplacementCharacters.Default.Replacements;
}
//First 4 are mandatory
for (int i = 0; i < Replacement.FIXED_COUNT; i++)
dict[i].Mandatory = true;
return new ReplacementCharacters { Replacements = dict };
}
@ -85,71 +218,5 @@ namespace FileManager
obj.WriteTo(writer);
}
}
[JsonConverter(typeof(ReplacementCharactersConverter))]
public class ReplacementCharacters
{
public static readonly ReplacementCharacters Default = new()
{
Replacements = new()
{
Replacement.OtherInvalid("_"),
Replacement.OpenQuote("“"),
Replacement.CloseQuote("”"),
Replacement.OtherQuote(""),
Replacement.Colon(""),
Replacement.Asterisk("✱"),
Replacement.QuestionMark(""),
Replacement.OpenAngleBracket(""),
Replacement.CloseAngleBracket(""),
Replacement.Pipe("⏐"),
}
};
public static readonly ReplacementCharacters LoFiDefault = new()
{
Replacements = new()
{
Replacement.OtherInvalid("_"),
Replacement.OpenQuote("'"),
Replacement.CloseQuote("'"),
Replacement.OtherQuote("'"),
Replacement.Colon("-"),
Replacement.Asterisk(""),
Replacement.QuestionMark(""),
Replacement.OpenAngleBracket("["),
Replacement.CloseAngleBracket("]"),
Replacement.Pipe("_"),
}
};
public List<Replacement> Replacements { get; init; }
public string DefaultReplacement => Replacements[0].ReplacementString;
public string OpenQuote => Replacements[1].ReplacementString;
public string CloseQuote => Replacements[2].ReplacementString;
public string OtherQuote => Replacements[3].ReplacementString;
private const char QuoteMark = '"';
public string GetReplacement(char toReplace, char preceding, char succeding)
{
if (toReplace == QuoteMark)
{
if (preceding != default && !char.IsLetter(preceding) && !char.IsNumber(preceding))
return OpenQuote;
else if (succeding != default && !char.IsLetter(succeding) && !char.IsNumber(succeding))
return CloseQuote;
else
return OtherQuote;
}
for (int i = 4; i < Replacements.Count; i++)
{
var r = Replacements[i];
if (r.CharacterToReplace == toReplace)
return r.ReplacementString;
}
return DefaultReplacement;
}
}
#endregion
}