using System; using System.Linq; using System.Globalization; using System.Collections.Generic; using BFR.Helpers; using BFR.DataModels; using Avalonia; namespace BFR.Operations { public class Sort : Operation { public override string Name => nameof(Sort); public override string Description => "Sorts the file names according to how they are when they arrive at this operation."; public static readonly AvaloniaProperty ModeProperty = AvaloniaProperty.Register(nameof(Mode), SortMode.Normal); public SortMode Mode { get => GetValue(ModeProperty); set => SetValue(ModeProperty, value); } public SortDirection Direction { get; set; } public SortOperationMode[] Modes => SortOperationMode.Modes; public string[] Directions => Enum.GetNames(typeof(SortDirection)); public bool FullName { get; set; } = false; private Func GetName => FullName ? (Func)(x => x.FullName) : (Func)(x => x.Name); protected override void ApplyToInternal(IList files) { // Fail conditions: No fail conditions. // Apply operation files.ReplaceAll(new List (Mode switch { SortMode.Normal => files.OrderBy(GetName, StringComparer.CurrentCulture), SortMode.Natural => files.OrderBy(GetName, Comparer.Create(CompareNatural)), SortMode.Ordinal => files.OrderBy(GetName, StringComparer.Ordinal), SortMode.Length => files.OrderBy(x => GetName(x).Length), SortMode.Reverse => files.Reverse(), _ => files })); if (Mode != SortMode.Reverse && Direction == SortDirection.Descending) files.ReplaceAll(new List(files.Reverse())); } // Code taken and slightly modified from https://git.lastassault.de/speatzle/BulkFileRenamer public static int CompareNatural(string strA, string strB) => CompareNatural(strA, strB, CultureInfo.CurrentCulture, CompareOptions.IgnoreCase); public static int CompareNatural(string strA, string strB, CultureInfo culture, CompareOptions options) { var cmp = culture.CompareInfo; var (iA, iB) = (0, 0); var softResult = 0; var softResultWeight = 0; while (iA < strA.Length && iB < strB.Length) { var isDigitA = char.IsDigit(strA[iA]); var isDigitB = char.IsDigit(strB[iB]); if (isDigitA != isDigitB) return cmp.Compare(strA, iA, strB, iB, options); else if (!isDigitA && !isDigitB) { var jA = iA + 1; var jB = iB + 1; while (jA < strA.Length && !char.IsDigit(strA[jA])) jA++; while (jB < strB.Length && !char.IsDigit(strB[jB])) jB++; var cmpResult = cmp.Compare(strA, iA, jA - iA, strB, iB, jB - iB, options); if (cmpResult != 0) { var (secA, secB) = (strA[iA..jA], strB[iB..jB]); if (cmp.Compare(secA + "1", secB + "2", options) == cmp.Compare(secA + "2", secB + "1", options)) return cmp.Compare(strA, iA, strB, iB, options); else if (softResultWeight < 1) { softResult = cmpResult; softResultWeight = 1; } } (iA, jA) = (iB, jB); } else { var zeroA = (char)(strA[iA] - (int)char.GetNumericValue(strA[iA])); var zeroB = (char)(strB[iB] - (int)char.GetNumericValue(strB[iB])); var (jA, jB) = (iA, iB); while (jA < strA.Length && strA[jA] == zeroA) jA++; while (jB < strB.Length && strB[jB] == zeroB) jB++; int resultIfSameLength = 0; do { isDigitA = jA < strA.Length && char.IsDigit(strA[jA]); isDigitB = jB < strB.Length && char.IsDigit(strB[jB]); var numA = isDigitA ? (int)char.GetNumericValue(strA[jA]) : 0; var numB = isDigitB ? (int)char.GetNumericValue(strB[jB]) : 0; if (isDigitA && (char)(strA[jA] - numA) != zeroA) isDigitA = false; if (isDigitB && (char)(strB[jB] - numB) != zeroB) isDigitB = false; if (isDigitA && isDigitB) { if (numA != numB && resultIfSameLength == 0) resultIfSameLength = numA < numB ? -1 : 1; jA++; jB++; } } while (isDigitA && isDigitB); if (isDigitA != isDigitB) return isDigitA ? 1 : -1; else if (resultIfSameLength != 0) return resultIfSameLength; var (lA, lB) = (jA - iA, jB - iB); if (lA != lB) return lA > lB ? -1 : 1; else if (zeroA != zeroB && softResultWeight < 2) { softResult = cmp.Compare(strA, iA, 1, strB, iB, 1, options); softResultWeight = 2; } (iA, iB) = (jA, jB); } } if (iA < strA.Length || iB < strB.Length) return iA < strA.Length ? 1 : -1; else if (softResult != 0) return softResult; return 0; } } }