Files
Umbraco-CMS/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/SimilarNodeName.cs

243 lines
8.3 KiB
C#
Raw Normal View History

using System.Collections.Generic;
2021-09-15 13:40:08 +02:00
using System.Globalization;
2017-09-15 18:22:19 +02:00
using System.Linq;
using System.Text.RegularExpressions;
using Umbraco.Extensions;
using static Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement.SimilarNodeName;
2017-09-15 18:22:19 +02:00
namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
2017-09-15 18:22:19 +02:00
{
internal class SimilarNodeName
{
public int Id { get; set; }
2017-11-15 08:53:20 +01:00
public string Name { get; set; }
2017-09-15 18:22:19 +02:00
public static string GetUniqueName(IEnumerable<SimilarNodeName> names, int nodeId, string nodeName)
{
var items = names
.Where(x => x.Id != nodeId) // ignore same node
.Select(x => x.Name);
2020-10-21 20:57:21 +01:00
var uniqueName = GetUniqueName(items, nodeName);
return uniqueName;
}
public static string GetUniqueName(IEnumerable<string> names, string name)
2017-09-15 18:22:19 +02:00
{
var model = new StructuredName(name);
var items = names
.Where(x => x.InvariantStartsWith(model.Text)) // ignore non-matching names
.Select(x => new StructuredName(x));
// name is empty, and there are no other names with suffixes, so just return " (1)"
if (model.IsEmptyName() && !items.Any())
2017-09-15 18:22:19 +02:00
{
2020-10-21 20:57:21 +01:00
model.Suffix = StructuredName.INITIAL_SUFFIX;
2017-09-15 18:22:19 +02:00
return model.FullName;
}
2017-09-15 18:22:19 +02:00
// name is empty, and there are other names with suffixes
if (model.IsEmptyName() && items.SuffixedNameExists())
{
var emptyNameSuffix = GetSuffixNumber(items);
if (emptyNameSuffix > 0)
{
2020-10-21 20:57:21 +01:00
model.Suffix = (uint?)emptyNameSuffix;
2018-03-22 11:24:12 +01:00
return model.FullName;
}
}
2017-09-15 18:22:19 +02:00
// no suffix - name without suffix does NOT exist - we can just use the name without suffix.
if (!model.Suffix.HasValue && !items.SimpleNameExists(model.Text))
{
model.Suffix = StructuredName.NO_SUFFIX;
return model.FullName;
}
// suffix - name with suffix does NOT exist
// We can just return the full name as it is as there's no conflict.
if (model.Suffix.HasValue && !items.SimpleNameExists(model.FullName))
{
return model.FullName;
}
// no suffix - name without suffix does NOT exist, AND name with suffix does NOT exist
2020-10-21 20:57:21 +01:00
if (!model.Suffix.HasValue && !items.SimpleNameExists(model.Text) && !items.SuffixedNameExists())
{
2020-10-21 20:57:21 +01:00
model.Suffix = StructuredName.NO_SUFFIX;
2017-09-15 18:22:19 +02:00
return model.FullName;
2017-09-15 18:22:19 +02:00
}
// no suffix - name without suffix exists, however name with suffix does NOT exist
2020-10-21 20:57:21 +01:00
if (!model.Suffix.HasValue && items.SimpleNameExists(model.Text) && !items.SuffixedNameExists())
2017-09-15 18:22:19 +02:00
{
var firstSuffix = GetFirstSuffix(items);
2020-10-21 20:57:21 +01:00
model.Suffix = (uint?)firstSuffix;
return model.FullName;
2017-09-15 18:22:19 +02:00
}
// no suffix - name without suffix exists, AND name with suffix does exist
2020-10-21 20:57:21 +01:00
if (!model.Suffix.HasValue && items.SimpleNameExists(model.Text) && items.SuffixedNameExists())
2017-09-15 18:22:19 +02:00
{
var nextSuffix = GetSuffixNumber(items);
2020-10-21 20:57:21 +01:00
model.Suffix = (uint?)nextSuffix;
2017-09-15 18:22:19 +02:00
return model.FullName;
}
// no suffix - name without suffix does NOT exist, however name with suffix exists
2020-10-21 20:57:21 +01:00
if (!model.Suffix.HasValue && !items.SimpleNameExists(model.Text) && items.SuffixedNameExists())
{
var nextSuffix = GetSuffixNumber(items);
2020-10-21 20:57:21 +01:00
model.Suffix = (uint?)nextSuffix;
return model.FullName;
}
// has suffix - name without suffix exists
2020-10-21 20:57:21 +01:00
if (model.Suffix.HasValue && items.SimpleNameExists(model.Text))
{
var nextSuffix = GetSuffixNumber(items);
2020-10-21 20:57:21 +01:00
model.Suffix = (uint?)nextSuffix;
return model.FullName;
}
// has suffix - name without suffix does NOT exist
// a case where the user added the suffix, so add a secondary suffix
2020-10-21 20:57:21 +01:00
if (model.Suffix.HasValue && !items.SimpleNameExists(model.Text))
{
2020-10-21 20:57:21 +01:00
model.Text = model.FullName;
model.Suffix = StructuredName.NO_SUFFIX;
2017-09-15 18:22:19 +02:00
// filter items based on full name with suffix
items = items.Where(x => x.Text.InvariantStartsWith(model.FullName));
var secondarySuffix = GetFirstSuffix(items);
2020-10-21 20:57:21 +01:00
model.Suffix = (uint?)secondarySuffix;
2017-09-15 18:22:19 +02:00
return model.FullName;
}
// has suffix - name without suffix also exists, therefore we simply increment
2020-10-21 20:57:21 +01:00
if (model.Suffix.HasValue && items.SimpleNameExists(model.Text))
{
var nextSuffix = GetSuffixNumber(items);
2020-10-21 20:57:21 +01:00
model.Suffix = (uint?)nextSuffix;
2017-09-15 18:22:19 +02:00
return model.FullName;
}
2017-09-15 18:22:19 +02:00
return name;
}
2017-09-15 18:22:19 +02:00
private static int GetFirstSuffix(IEnumerable<StructuredName> items)
{
const int suffixStart = 1;
2020-10-21 20:57:21 +01:00
if (!items.Any(x => x.Suffix == suffixStart))
{
// none of the suffixes are the same as suffixStart, so we can use suffixStart!
return suffixStart;
}
return GetSuffixNumber(items);
}
private static int GetSuffixNumber(IEnumerable<StructuredName> items)
{
int current = 1;
2020-10-21 20:57:21 +01:00
foreach (var item in items.OrderBy(x => x.Suffix))
{
2020-10-21 20:57:21 +01:00
if (item.Suffix == current)
2017-09-15 18:22:19 +02:00
{
current++;
}
2020-10-21 20:57:21 +01:00
else if (item.Suffix > current)
{
// do nothing - we found our number!
// eg. when suffixes are 1 & 3, then this method is required to generate 2
break;
2017-09-15 18:22:19 +02:00
}
}
return current;
2017-09-15 18:22:19 +02:00
}
internal class StructuredName
2017-09-15 18:22:19 +02:00
{
2020-10-21 20:57:21 +01:00
const string SPACE_CHARACTER = " ";
const string SUFFIXED_PATTERN = @"(.*) \(([1-9]\d*)\)$";
internal const uint INITIAL_SUFFIX = 1;
internal static readonly uint? NO_SUFFIX = default;
internal string Text { get; set; }
internal uint? Suffix { get; set; }
public string FullName
{
get
{
2020-10-21 20:57:21 +01:00
string text = (Text == SPACE_CHARACTER) ? Text.Trim() : Text;
2020-10-21 20:57:21 +01:00
return Suffix > 0 ? $"{text} ({Suffix})" : text;
}
}
internal StructuredName(string name)
2017-09-15 18:22:19 +02:00
{
if (string.IsNullOrWhiteSpace(name))
{
Text = SPACE_CHARACTER;
2017-09-15 18:22:19 +02:00
return;
}
2020-10-21 20:57:21 +01:00
var rg = new Regex(SUFFIXED_PATTERN);
var matches = rg.Matches(name);
if (matches.Count > 0)
2017-09-15 18:22:19 +02:00
{
var match = matches[0];
Text = match.Groups[1].Value;
2021-09-15 13:40:08 +02:00
int number = int.TryParse(match.Groups[2].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out number) ? number : 0;
2020-10-21 20:57:21 +01:00
Suffix = (uint?)(number);
return;
2017-09-15 18:22:19 +02:00
}
else
2017-09-15 18:22:19 +02:00
{
Text = name;
2017-09-15 18:22:19 +02:00
}
}
internal bool IsEmptyName()
{
return string.IsNullOrWhiteSpace(Text);
}
}
}
internal static class ListExtensions
{
internal static bool Contains(this IEnumerable<StructuredName> items, StructuredName model)
{
return items.Any(x => x.FullName.InvariantEquals(model.FullName));
}
internal static bool SimpleNameExists(this IEnumerable<StructuredName> items, string name)
{
return items.Any(x => x.FullName.InvariantEquals(name));
}
internal static bool SuffixedNameExists(this IEnumerable<StructuredName> items)
{
2020-10-21 20:57:21 +01:00
return items.Any(x => x.Suffix.HasValue);
2017-09-15 18:22:19 +02:00
}
}
2017-09-23 10:08:18 +02:00
}