Fixed: Inserting literal { or } in renaming format using {{ or }}

fixes #3434
pull/3404/head
Taloth Saldono 5 years ago
parent 556bd11725
commit 9aa89a0df9

@ -232,6 +232,28 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
Subject.BuildFileName(new List<Episode> {_episode1}, _series, _episodeFile)
.Should().Be("South Park - S15E06 - City Sushi [HDTV-720p]");
}
[TestCase("Some Escaped {{ String", "Some Escaped { String")]
[TestCase("Some Escaped }} String", "Some Escaped } String")]
[TestCase("Some Escaped {{Series Title}} String", "Some Escaped {Series Title} String")]
[TestCase("Some Escaped {{{Series Title}}} String", "Some Escaped {South Park} String")]
public void should_escape_token_in_format(string format, string expected)
{
_namingConfig.StandardEpisodeFormat = format;
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be(expected);
}
[Test]
public void should_escape_token_in_title()
{
_namingConfig.StandardEpisodeFormat = "Some Unescaped {Series Title} String";
_series.Title = "My {Quality Full} Title";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("Some Unescaped My {Quality Full} Title String");
}
[Test]
public void use_file_name_when_sceneName_is_null()

@ -40,7 +40,7 @@ namespace NzbDrone.Core.Organizer
private readonly ICached<bool> _requiresAbsoluteEpisodeNumberCache;
private readonly Logger _logger;
private static readonly Regex TitleRegex = new Regex(@"\{(?<prefix>[- ._\[(]*)(?<token>(?:[a-z0-9]+)(?:(?<separator>[- ._]+)(?:[a-z0-9]+))?)(?::(?<customFormat>[a-z0-9]+))?(?<suffix>[- ._)\]]*)\}",
private static readonly Regex TitleRegex = new Regex(@"(?<escaped>\{\{|\}\})|\{(?<prefix>[- ._\[(]*)(?<token>(?:[a-z0-9]+)(?:(?<separator>[- ._]+)(?:[a-z0-9]+))?)(?::(?<customFormat>[a-z0-9]+))?(?<suffix>[- ._)\]]*)\}",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex EpisodeRegex = new Regex(@"(?<episode>\{episode(?:\:0+)?})",
@ -158,7 +158,7 @@ namespace NzbDrone.Core.Organizer
AddMediaInfoTokens(tokenHandlers, episodeFile);
AddPreferredWords(tokenHandlers, series, episodeFile, preferredWords);
var component = ReplaceTokens(splitPattern, tokenHandlers, namingConfig).Trim();
var component = ReplaceTokens(splitPattern, tokenHandlers, namingConfig, true).Trim();
var maxEpisodeTitleLength = 255 - GetLengthWithoutEpisodeTitle(component, namingConfig);
AddEpisodeTitleTokens(tokenHandlers, episodes, maxEpisodeTitleLength);
@ -548,8 +548,8 @@ namespace NzbDrone.Core.Organizer
private void AddEpisodeTitlePlaceholderTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers)
{
tokenHandlers["{Episode Title}"] = m => m.RegexMatch.Value;
tokenHandlers["{Episode CleanTitle}"] = m => m.RegexMatch.Value;
tokenHandlers["{Episode Title}"] = m => null;
tokenHandlers["{Episode CleanTitle}"] = m => null;
}
private void AddEpisodeTitleTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, List<Episode> episodes, int maxLength)
@ -709,13 +709,23 @@ namespace NzbDrone.Core.Organizer
}
}
private string ReplaceTokens(string pattern, Dictionary<string, Func<TokenMatch, string>> tokenHandlers, NamingConfig namingConfig)
private string ReplaceTokens(string pattern, Dictionary<string, Func<TokenMatch, string>> tokenHandlers, NamingConfig namingConfig, bool escape = false)
{
return TitleRegex.Replace(pattern, match => ReplaceToken(match, tokenHandlers, namingConfig));
return TitleRegex.Replace(pattern, match => ReplaceToken(match, tokenHandlers, namingConfig, escape));
}
private string ReplaceToken(Match match, Dictionary<string, Func<TokenMatch, string>> tokenHandlers, NamingConfig namingConfig)
private string ReplaceToken(Match match, Dictionary<string, Func<TokenMatch, string>> tokenHandlers, NamingConfig namingConfig, bool escape)
{
if (match.Groups["escaped"].Success)
{
if (escape)
return match.Value;
else if (match.Value == "{{")
return "{";
else if (match.Value == "}}")
return "}";
}
var tokenMatch = new TokenMatch
{
RegexMatch = match,
@ -733,7 +743,15 @@ namespace NzbDrone.Core.Organizer
var tokenHandler = tokenHandlers.GetValueOrDefault(tokenMatch.Token, m => string.Empty);
var replacementText = tokenHandler(tokenMatch).Trim();
var replacementText = tokenHandler(tokenMatch);
if (replacementText == null)
{
// Preserve original token if handler returned null
return match.Value;
}
replacementText = replacementText.Trim();
if (tokenMatch.Token.All(t => !char.IsLetter(t) || char.IsLower(t)))
{
@ -756,6 +774,11 @@ namespace NzbDrone.Core.Organizer
replacementText = tokenMatch.Prefix + replacementText + tokenMatch.Suffix;
}
if (escape)
{
replacementText = replacementText.Replace("{", "{{").Replace("}", "}}");
}
return replacementText;
}

Loading…
Cancel
Save