Merge pull request #1755 from Jo-Be-Co/Conditional-output-on-format-templates

#1754 Conditional output on format templates
This commit is contained in:
rmcrackan
2026-04-20 21:59:02 -04:00
committed by GitHub
4 changed files with 109 additions and 52 deletions

View File

@@ -4,6 +4,7 @@ using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using Dinah.Core;
namespace FileManager.NamingTemplate;
@@ -43,7 +44,7 @@ public static partial class CommonFormatters
if (string.IsNullOrWhiteSpace(formatString) || !StringFormatRegex().TryMatch(formatString, out var match)) return value;
// first shorten the string if a number is specified in the format string
if (int.TryParse(match.Groups["left"].ValueSpan, out var length) && length < value.Length)
if (match.TryParseInt("left", out var length) && length < value.Length)
value = value[..length];
culture ??= CultureInfo.CurrentCulture;
@@ -68,7 +69,7 @@ public static partial class CommonFormatters
// is this function is called from toString implementation of the IFormattable interface, we only get a IFormatProvider
var culture = GetCultureInfo(provider);
var oldUiCulture = Thread.CurrentThread.CurrentUICulture;
var result = CollapseSpacesAndTrimRegex().Replace(TagFormatRegex().ReplaceWithGaps(templateString, GetValueForMatchingTag, Unescape), "");
var result = CollapseSpacesAndTrimRegex().Replace(TagFormatRegex().ReplaceWithGaps(templateString, GetValueForMatchingTag, Unescape), string.Empty);
Thread.CurrentThread.CurrentUICulture = oldUiCulture;
return result;
@@ -77,17 +78,19 @@ public static partial class CommonFormatters
var tag = m.Groups["tag"].Value;
if (!replacements.TryGetValue(tag, out var getter)) return m.Value;
var lang = m.Groups["lang"].ValueOrNull();
var lang = m.ResolveValue("lang");
var cultureToUse = lang is null ? culture : CultureInfo.GetCultureInfo(lang);
Thread.CurrentThread.CurrentUICulture = cultureToUse ?? oldUiCulture;
var value = getter(toFormat);
var format = m.Groups["format"].ValueOrNull();
return value switch
var format = m.ResolveValue("format");
var formatted = value switch
{
IFormattable formattable => formattable.ToString(format, cultureToUse),
_ => _StringFormatter(value?.ToString(), format, cultureToUse),
};
return formatted.IsNullOrEmpty() ? string.Empty : m.UnescapeValue("pre") + formatted + m.UnescapeValue("post");
}
}
@@ -112,6 +115,12 @@ public static partial class CommonFormatters
| "[^"]*" # - allow "string" to be included in the format, with "" being an escaped " character
| [^\\'"] # - match any other character. This will not catch the tag format at first. Because ...
) *? ) # With *? the pattern above tries not to consume the tag format.
(?: \{ (?<pre> (?: # With an optional extra '{' surroundings of the tag format might be specified in a group called '<pre>'.
\\. # - '\' escapes always the next character. Especially further '\' and the opening '{'
| '[^']*' # - allow 'string' to be included in the format
| "[^"]*" # - allow "string" to be included in the format
| [^\\'"{}] # - match any other character. We also don't want further unescaped '{' or '}' in the format.
) * ) )? # Capture all up to the next unescaped '{' to get the optional '<pre>' group.
\{ (?<tag> [A-Z0-9]+ | \#) # Capture the tags name as '<tag>'. It is always enclosed in '{' and '}'. It may only contain letters and numbers or be a single '#'.
(?:@(?<lang>[a-z-]+))? # Introduced by '@' the tag name may optionally be followed by a language specifier captured in a group called '<lang>'.
(?::(?<format>(?:
@@ -121,7 +130,17 @@ public static partial class CommonFormatters
| .
) *? ))?
\}
""", RegexOptions.IgnoreCase)]
(?(pre)
(?<post>(?:
\\.
| '[^']*'
| "[^"]*"
| [^\\'"{}]
) * ) \}
|
)
""",
RegexOptions.IgnoreCase)]
public static partial Regex TagFormatRegex();
public static string FormattableFormatter(ITemplateTag _, IFormattable? value, string? formatString, CultureInfo? culture)

View File

@@ -10,8 +10,8 @@ public static class RegExpExtensions
extension(Group group)
{
public string? ValueOrNull() => group.Success ? group.Value : null;
public string? UnescapeValueOrNull() => group.Success ? CommonFormatters.Unescape(group.ValueSpan, []) : null;
public string UnescapeValue() => group.Success ? CommonFormatters.Unescape(group.ValueSpan, []) : string.Empty;
public string? UnescapeValueOrNull() => group.Success ? CommonFormatters.Unescape(group.ValueSpan, ['\'', '"']) : null;
public string UnescapeValue() => group.Success ? CommonFormatters.Unescape(group.ValueSpan, ['\'', '"']) : string.Empty;
public ReadOnlySpan<char> ValueSpanOrNull() => group.Success ? group.ValueSpan : null;
}

View File

@@ -416,6 +416,10 @@ namespace TemplatesTests
[DataRow("<author[max(1) format(foo)]>", "Jill Conner Browne")]
[DataRow("<author[max(1) format('{M}')]>", "Jill Conner Browne")]
[DataRow(@"(<author[separator(\)() format(')['{M}\]\()]>)", "()[Conner]()()[E.]()()[John]()()[Maud]()()[]()()[]()()[]()")]
[DataRow("<author[separator(; ) format({M})]>", "Conner; E.; John; Maud; ; ;")]
[DataRow(@"<author[separator(; ) format([\{{M}\}\])]>", "[{Conner}]; [{E.}]; [{John}]; [{Maud}]; [{}]; [{}]; [{}]")]
[DataRow(@"<author[separator(; ) format({[\{{M:\u}\}\]})]>", "[{Conner}]; [{E.}]; [{John}]; [{Maud}]; ; ;")]
[DataRow(@"<author[slice(4..5) separator(, ) format({{F}'-'}{{M}\-}{L:U}{-[{ID}\]})]>", "Lucy-Maud-MONTGOMERY-[B4], Jon-BON JOVI-[B5]")]
[DataRow("<first author>", "Jill Conner Browne")]
[DataRow("<first author[]>", "Jill Conner Browne")]
[DataRow("<first author[{L}, {F}]>", "Browne, Jill")]

View File

@@ -66,13 +66,13 @@ Anything between the opening tag (`<tagname->`) and closing tag (`<-tagname>`) w
| \<if series-\>...\<-if series\> | Only include if part of a book series or podcast | Conditional |
| \<if podcast-\>...\<-if podcast\> | Only include if part of a podcast | Conditional |
| \<if bookseries-\>...\<-if bookseries\> | Only include if part of a book series | Conditional |
| \<if podcastparent-\>...\<-if podcastparent\>**†** | Only include if item is a podcast series parent | Conditional |
| \<if podcastparent-\>...\<-if podcastparent\> **†** | Only include if item is a podcast series parent | Conditional |
| \<if abridged-\>...\<-if abridged\> | Only include if item is abridged | Conditional |
| \<has PROPERTY-\>...\<-has\> | Only include if the PROPERTY has a value (i.e. not null or empty) | Conditional |
| \<is PROPERTY[[CHECK](#checks)]-\>...\<-is\> | Only include if the PROPERTY or a single value of a list PROPERTY satisfies the CHECK | Conditional |
| \<is PROPERTY[FORMAT][[CHECK](#checks)]-\>...\<-is\> | Only include if the formatted PROPERTY or a single value of a list PROPERTY satisfies the CHECK | Conditional |
| \<is PROPERTY[...separator(...)...][[CHECK](#checks)]-\>...\<-is\> | Only include if the joined form of all formatted values of a list PROPERTY satisfies the CHECK | Conditional |
| \<cmp 1st-PROPERTY [[CHECK](#checks)] 2nd-PROPERTY-\>...\<-cmp\> | Only include if two given PROPERTIES satisfy the CHECK | Conditional |
| \<cmp 1st-PROPERTY [[CHECK](#checks)] 2nd-PROPERTY-\>...\<-cmp\> | Only include if two given PROPERTIES satisfy the CHECK | Conditional |
**†** Only affects the podcast series folder naming if "Save all podcast episodes to the series parent folder" option is checked.
@@ -85,7 +85,7 @@ You can invert the condition (instead of displaying the text when the condition
| \<!if series-\>...\<-if series\> | Only include if _not_ part of a book series or podcast | Conditional |
| \<!if podcast-\>...\<-if podcast\> | Only include if _not_ part of a podcast | Conditional |
| \<!if bookseries-\>...\<-if bookseries\> | Only include if _not_ part of a book series | Conditional |
| \<!if podcastparent-\>...\<-if podcastparent\>**†** | Only include if item is _not_ a podcast series parent | Conditional |
| \<!if podcastparent-\>...\<-if podcastparent\> **†** | Only include if item is _not_ a podcast series parent | Conditional |
| \<!has PROPERTY-\>...\<-has\> | Only include if the PROPERTY _does not_ have a value (i.e. is null or empty) | Conditional |
| \<!is PROPERTY[[CHECK](#checks)]-\>...\<-is\> | Only include if neither the whole PROPERTY nor the values of a list PROPERTY satisfies the CHECK | Conditional |
| \<!cmp 1st-PROPERTY [CHECK] 2nd-PROPERTY-\>...\<-cmp\> | Only include if two given PROPERTIES _do not_ satisfy the CHECK | Conditional |
@@ -127,49 +127,59 @@ Text formatting can change length and case of the text. Use \<#\>, \<#\>\<case\>
### Text List Formatters
| Formatter | Description | Example Usage | Example Result |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |--------------------------------------------- | ---------------------------------------------|
| separator() | Specify the text used to join<br>multiple entries.<br><br>Default is ", " | `<tag[separator(_)]>` | Tag1_Tag2_Tag3_Tag4_Tag5 |
| format(\{S\}) | Formats the entries by placing their values into the specified template.<br>Use {S:[Text_Formatter](#text-formatters)} to place the entry and optionally apply a format. | `<tag[format(Tag={S:l})`<br>`separator(;)]>` | Tag=tag1;Tag=tag2;Tag=tag3;Tag=tag4;Tag=tag5 |
| sort(S) | Sorts the elements by their value.<br><br>*Sorting direction:*<br>uppercase = ascending<br>lowercase = descending<br><br>Default is unsorted | `<tag[sort(s)`<br>`separator(;)]>` | Tag5;Tag4;Tag3;Tag2;Tag1 |
| max(#) | Only use the first # of entries | `<tag[max(1)]>` | Tag1 |
| slice(#) | Only use the nth entry of the list | `<tag[slice(2)]>` | Tag2 |
| slice(#..) | Only use entries of the list starting from # | `<tag[slice(2..)]>` | Tag2, Tag3, Tag4, Tag5 |
| slice(..#) | Like max(#). Only use the first # of entries | `<tag[slice(..1)]>` | Tag1 |
| slice(#..#) | Only use entries of the list starting from # and ending at # (inclusive) | `<tag[slice(2..4)]>` | Tag2, Tag3, Tag4 |
| slice(-#..-#) | Numbers may be specified negative. In that case positions ar counted from the end with -1 pointing at the last member | `<tag[slice(-3..-2)]>` | Tag3, Tag4 |
| Formatter | Description | Example Usage | Example Result |
|----------------------| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |--------------------------------------------- | ---------------------------------------------|
| separator() | Specify the text used to join<br>multiple entries.<br><br>Default is ", " | `<tag[separator(_)]>` | Tag1_Tag2_Tag3_Tag4_Tag5 |
| format(\{S\}) **†** | Formats the entries by placing their values into the specified template.<br>Use {S:[Text_Formatter](#text-formatters)} to place the entry and optionally apply a format. | `<tag[format(Tag={S:l})`<br>`separator(;)]>` | Tag=tag1;Tag=tag2;Tag=tag3;Tag=tag4;Tag=tag5 |
| sort(S) | Sorts the elements by their value.<br><br>*Sorting direction:*<br>uppercase = ascending<br>lowercase = descending<br><br>Default is unsorted | `<tag[sort(s)`<br>`separator(;)]>` | Tag5;Tag4;Tag3;Tag2;Tag1 |
| max(#) | Only use the first # of entries | `<tag[max(1)]>` | Tag1 |
| slice(#) | Only use the nth entry of the list | `<tag[slice(2)]>` | Tag2 |
| slice(#..) | Only use entries of the list starting from # | `<tag[slice(2..)]>` | Tag2, Tag3, Tag4, Tag5 |
| slice(..#) | Like max(#). Only use the first # of entries | `<tag[slice(..1)]>` | Tag1 |
| slice(#..#) | Only use entries of the list starting from # and ending at # (inclusive) | `<tag[slice(2..4)]>` | Tag2, Tag3, Tag4 |
| slice(-#..-#) | Numbers may be specified negative. In that case positions ar counted from the end with -1 pointing at the last member | `<tag[slice(-3..-2)]>` | Tag3, Tag4 |
**†** For further information on format templates, please refer to the [Format_Templates](#format-templates) section.
### Series Formatters
| Formatter | Description | Example Usage | Example Result |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| \{N \| # \| ID\} | Formats the series using<br>the series part tags.<br>\{N:[Text_Formatter](#text-formatters)\} = Series Name<br>\{#:[Number_Formatter](#number-formatters)\} = Number order in series<br>\{ID:[Text_Formatter](#text-formatters)\} = Audible Series ID<br><br>Formatter parts are optional and introduced by the colon. If specified the string will be used to format the part using the corresponding formatter.<br><br>Default is \{N\} | `<first series>`\<hr\>`<first series[{N:l}]>`\<hr\>`<first series[{N}, {#}, {ID}]>`\<hr\>`<first series[{N:10U}, {ID}, {#:00.0}]>` | Sherlock Holmes\<hr\>sherlock holmes\<hr\>Sherlock Holmes, 1-6, B08376S3R2\<hr\>SHERLOCK H, B08376S3R2, 01.0-06.0 |
| Formatter | Description | Example Usage | Example Result |
|-------------------------| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| \{N \| # \| ID\} **†** | Formats the series using<br>the series part tags.<br>\{N:[Text_Formatter](#text-formatters)\} = Series Name<br>\{#:[Number_Formatter](#number-formatters)\} = Number order in series<br>\{ID:[Text_Formatter](#text-formatters)\} = Audible Series ID<br><br>Formatter parts are optional and introduced by the colon. If specified the string will be used to format the part using the corresponding formatter.<br><br>Default is \{N\} | `<first series>`\<hr\>`<first series[{N:l}]>`\<hr\>`<first series[{N}, {#}, {ID}]>`\<hr\>`<first series[{N:10U}, {ID}, {#:00.0}]>` | Sherlock Holmes\<hr\>sherlock holmes\<hr\>Sherlock Holmes, 1-6, B08376S3R2\<hr\>SHERLOCK H, B08376S3R2, 01.0-06.0 |
**†** For further information on format templates, please refer to the [Format_Templates](#format-templates) section.
### Series List Formatters
| Formatter | Description | Example Usage | Example Result |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| separator() | Specify the text used to join<br>multiple series names.<br><br>Default is ", " | `<series[separator(; )]>` | Sherlock Holmes; Some Other Series |
| format(\{N \| # \| ID\}) | Formats the series properties<br>using the name series tags.<br>See [Series Formatter Usage](#series-formatters) above. | `<series[format({N}, {#})`<br>`separator(; )]>`\<hr\>`<series[format({ID}-{N}, {#:00.0})]>` | Sherlock Holmes, 1-6; Book Collection, 1\<hr\>B08376S3R2-Sherlock Holmes, 01.0-06.0, B000000000-Book Collection, 01.0 |
| sort(N \| # \| ID) | Sorts the series by name, number or ID.<br><br>These terms define the primary, secondary, tertiary, … sorting order.<br>You may combine multiple terms in sequence to specify multilevel sorting.<br><br>*Sorting direction:*<br>uppercase = ascending<br>lowercase = descending<br><br>Default is unsorted | `<series[sort(N)`<br>`separator(; )]>` | Book Collection, 1; Sherlock Holmes, 1-6 |
| max(#) | Only use the first # of series | `<series[max(1)]>` | Sherlock Holmes |
| slice(#..#) | Only use entries of the series list starting from # and ending at # (inclusive)<br><br>See [Text List Formatter Usage](#Text-List-Formatters) above for details on all the variants of `slice()` | `<series[slice(..-2)]>` | Sherlock Holmes |
| Formatter | Description | Example Usage | Example Result |
|---------------------------------| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| separator() | Specify the text used to join<br>multiple series names.<br><br>Default is ", " | `<series[separator(; )]>` | Sherlock Holmes; Some Other Series |
| format(\{N \| # \| ID\}) **†** | Formats the series properties<br>using the name series tags.<br>See [Series Formatter Usage](#series-formatters) above. | `<series[format({N}, {#})`<br>`separator(; )]>`\<hr\>`<series[format({ID}-{N}, {#:00.0})]>` | Sherlock Holmes, 1-6; Book Collection, 1\<hr\>B08376S3R2-Sherlock Holmes, 01.0-06.0, B000000000-Book Collection, 01.0 |
| sort(N \| # \| ID) | Sorts the series by name, number or ID.<br><br>These terms define the primary, secondary, tertiary, … sorting order.<br>You may combine multiple terms in sequence to specify multilevel sorting.<br><br>*Sorting direction:*<br>uppercase = ascending<br>lowercase = descending<br><br>Default is unsorted | `<series[sort(N)`<br>`separator(; )]>` | Book Collection, 1; Sherlock Holmes, 1-6 |
| max(#) | Only use the first # of series | `<series[max(1)]>` | Sherlock Holmes |
| slice(#..#) | Only use entries of the series list starting from # and ending at # (inclusive)<br><br>See [Text List Formatter Usage](#Text-List-Formatters) above for details on all the variants of `slice()` | `<series[slice(..-2)]>` | Sherlock Holmes |
**†** For further information on format templates, please refer to the [Format_Templates](#format-templates) section.
### Name Formatters
| Formatter | Description | Example Usage | Example Result |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | --------------------------------------- |
| \{T \| F \| M \| L \| S \| ID\} | Formats the human name using<br>the name part tags.<br>\{T:[Text_Formatter](#text-formatters)\} = Title (e.g. "Dr.")<br>\{F:[Text_Formatter](#Text-Formatters)\} = First name<br>\{M:[Text_Formatter](#text-formatters)\} = Middle name<br>\{L:[Text_Formatter](#text-formatters)\} = Last Name<br>\{S:[Text_Formatter](#text-formatters)\} = Suffix (e.g. "PhD")<br>\{ID:[Text_Formatter](#text-formatters)\} = Audible Contributor ID<br><br>Formatter parts are optional and introduced by the colon. If specified the string will be used to format the part using the correspoing formatter.<br><br>Default is \{T\} \{F\} \{M\} \{L\} \{S\} | `<first narrator[{L}, {F:1}.]>`\<hr\>`<first author[{L:u}, {F} _{ID}_]>` | Fry, S.\<hr\>DOYLE, Arthur \_B000AQ43GQ\_ |
| Formatter | Description | Example Usage | Example Result |
|----------------------------------------| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | --------------------------------------- |
| \{T \| F \| M \| L \| S \| ID\} **†** | Formats the human name using<br>the name part tags.<br>\{T:[Text_Formatter](#text-formatters)\} = Title (e.g. "Dr.")<br>\{F:[Text_Formatter](#Text-Formatters)\} = First name<br>\{M:[Text_Formatter](#text-formatters)\} = Middle name<br>\{L:[Text_Formatter](#text-formatters)\} = Last Name<br>\{S:[Text_Formatter](#text-formatters)\} = Suffix (e.g. "PhD")<br>\{ID:[Text_Formatter](#text-formatters)\} = Audible Contributor ID<br><br>Formatter parts are optional and introduced by the colon. If specified the string will be used to format the part using the correspoing formatter.<br><br>Default is \{T\} \{F\} \{M\} \{L\} \{S\} | `<first narrator[{L}, {F:1}.]>`\<hr\>`<first author[{L:u}, {F} _{ID}_]>` | Fry, S.\<hr\>DOYLE, Arthur \_B000AQ43GQ\_ |
**†** For further information on format templates, please refer to the [Format_Templates](#format-templates) section.
### Name List Formatters
| Formatter | Description | Example Usage | Example Result |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| separator() | Specify the text used to join<br>multiple people's names.<br><br>Default is ", " | `<author[separator(; )]>` | Arthur Conan Doyle; Stephen Fry |
| format(\{T \| F \| M \| L \| S \| ID\}) | Formats the human name using<br>the name part tags.<br>See [Name Formatter Usage](#name-formatters) above. | `<author[format({L:u}, {F})`<br>`separator(; )]>`\<hr\>`<author[format({L}, {F:1}.`<br>`_{ID}_) separator(; )]>` | DOYLE, Arthur; FRY, Stephen\<hr\>Doyle, A. \_B000AQ43GQ\_;<br>Fry, S. \_B000APAGVS\_ |
| sort(T \| F \| M \| L \| S \| ID) | Sorts the names by title,<br> first, middle, or last name,<br>suffix or Audible Contributor ID<br><br>These terms define the primary, secondary, tertiary, … sorting order.<br>You may combine multiple terms in sequence to specify multilevel sorting.<br><br>*Sorting direction:*<br>uppercase = ascending<br>lowercase = descending<br><br>Default is unsorted | `<author[sort(M)]>`\<hr\>`<author[sort(Fl)]>`\<hr\>`<author[sort(L FM ID)]>` | Stephen Fry, Arthur Conan Doyle\<hr\>Stephen King, Stephen Fry\<hr\>John P. Smith \_B000TTTBBB\_, John P. Smith \_B000TTTCCC\_, John S. Smith \_B000HHHVVV\_ |
| max(#) | Only use the first # of names<br><br>Default is all names | `<author[max(1)]>` | Arthur Conan Doyle |
| slice(#..#) | Only use entries of the names list starting from # and ending at # (inclusive)<br><br>See [Text List Formatter Usage](#Text-List-Formatters) above for details on all the variants of `slice()` | `<author[slice(..-2)]>` | Arthur Conan Doyle |
| Formatter | Description | Example Usage | Example Result |
|------------------------------------------------| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| separator() | Specify the text used to join<br>multiple people's names.<br><br>Default is ", " | `<author[separator(; )]>` | Arthur Conan Doyle; Stephen Fry |
| format(\{T \| F \| M \| L \| S \| ID\}) **†** | Formats the human name using<br>the name part tags.<br>See [Name Formatter Usage](#name-formatters) above. | `<author[format({L:u}, {F})`<br>`separator(; )]>`\<hr\>`<author[format({L}, {F:1}.`<br>`_{ID}_) separator(; )]>` | DOYLE, Arthur; FRY, Stephen\<hr\>Doyle, A. \_B000AQ43GQ\_;<br>Fry, S. \_B000APAGVS\_ |
| sort(T \| F \| M \| L \| S \| ID) | Sorts the names by title,<br> first, middle, or last name,<br>suffix or Audible Contributor ID<br><br>These terms define the primary, secondary, tertiary, … sorting order.<br>You may combine multiple terms in sequence to specify multilevel sorting.<br><br>*Sorting direction:*<br>uppercase = ascending<br>lowercase = descending<br><br>Default is unsorted | `<author[sort(M)]>`\<hr\>`<author[sort(Fl)]>`\<hr\>`<author[sort(L FM ID)]>` | Stephen Fry, Arthur Conan Doyle\<hr\>Stephen King, Stephen Fry\<hr\>John P. Smith \_B000TTTBBB\_, John P. Smith \_B000TTTCCC\_, John S. Smith \_B000HHHVVV\_ |
| max(#) | Only use the first # of names<br><br>Default is all names | `<author[max(1)]>` | Arthur Conan Doyle |
| slice(#..#) | Only use entries of the names list starting from # and ending at # (inclusive)<br><br>See [Text List Formatter Usage](#Text-List-Formatters) above for details on all the variants of `slice()` | `<author[slice(..-2)]>` | Arthur Conan Doyle |
**†** For further information on format templates, please refer to the [Format_Templates](#format-templates) section.
### TimeSpan Formatters
For more custom formatters and examples, [see this guide from Microsoft](https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-timespan-format-strings).
@@ -228,21 +238,45 @@ You can use custom formatters to construct customized DateTime string. For more
You can specify which part of a region you are interested in.
| Formatter | Description | Example Usage | Example Result |
|-------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------|---------------------------------------|
| \{O \| I \| I2 \| I3 \| E \| N \| W \| L \| T \| ID\} | Formats the region using<br>the region part tags.<br>\{O:[Text_Formatter](#text-formatters)\} = Region as used in Libation<br>\{I:[Text_Formatter](#text-formatters)\} = Two letter ISO code<br>\{I2:[Text_Formatter](#text-formatters)\} = Two letter ISO code<br>\{I3:[Text_Formatter](#text-formatters)\} = Three letter ISO code<br>\{E:[Text_Formatter](#text-formatters)\} = English name<br>\{N:[Text_Formatter](#text-formatters)\} = Native name - OS dependent<br>\{W:[Text_Formatter](#number-formatters)\} = Unique Windows code<br>\{L:[Text_Formatter](#text-formatters)\} = Lang code used for this region/store<br>\{T:[Text_Formatter](#number-formatters)\} = TLD under which the audible store is hosted<br>\{ID:[Text_Formatter](#text-formatters)\} = Region code<br> <br><br>Formatter parts are optional and introduced by the colon. If specified the string will be used to format the part using the corresponding formatter.<br><br>Default is \{O\} | `<locale[{I} ({E})]>`<hr>`www.audible.<locale[{T}]>` | US (United States)<hr>www.audible.com |
| \{D\} **†** | Display name interpreted by the current language settings.<br>To ensure output in a specific language the lang-code to use might be specified with a leading '@'.<br>Formatter part is also optional and introduced by the colon.<br>\{D@LANG:[Text_Formatter](#text-formatters)\} | `<locale[{D@es:u}]>` | ESTADOS UNIDOS |
| Formatter | Description | Example Usage | Example Result |
|-------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------|---------------------------------------|
| \{O \| I \| I2 \| I3 \| E \| N \| W \| L \| T \| ID\} **†** | Formats the region using<br>the region part tags.<br>\{O:[Text_Formatter](#text-formatters)\} = Region as used in Libation<br>\{I:[Text_Formatter](#text-formatters)\} = Two letter ISO code<br>\{I2:[Text_Formatter](#text-formatters)\} = Two letter ISO code<br>\{I3:[Text_Formatter](#text-formatters)\} = Three letter ISO code<br>\{E:[Text_Formatter](#text-formatters)\} = English name<br>\{N:[Text_Formatter](#text-formatters)\} = Native name - OS dependent<br>\{W:[Text_Formatter](#number-formatters)\} = Unique Windows code<br>\{L:[Text_Formatter](#text-formatters)\} = Lang code used for this region/store<br>\{T:[Text_Formatter](#number-formatters)\} = TLD under which the audible store is hosted<br>\{ID:[Text_Formatter](#text-formatters)\} = Region code<br> <br><br>Formatter parts are optional and introduced by the colon. If specified the string will be used to format the part using the corresponding formatter.<br><br>Default is \{O\} | `<locale[{I} ({E})]>`<hr>`www.audible.<locale[{T}]>` | US (United States)<hr>www.audible.com |
| \{D\} **†**,**‡** | Display name interpreted by the current language settings.<br>To ensure output in a specific language the lang-code to use might be specified with a leading '@'.<br>Formatter part is also optional and introduced by the colon.<br>\{D@LANG:[Text_Formatter](#text-formatters)\} | `<locale[{D@es:u}]>` | ESTADOS UNIDOS |
**†** LANG may be any code from the [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) standard like `es` for Spanish, `en` for English, `de` for German, etc. or even a [IETF language tag](https://en.wikipedia.org/wiki/IETF_language_tag) like 'fr-CA'.
**†** For further information on format templates, please refer to the [Format_Templates](#format-templates) section.
**‡** LANG may be any code from the [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) standard like `es` for Spanish, `en` for English, `de` for German, etc. or even a [IETF language tag](https://en.wikipedia.org/wiki/IETF_language_tag) like 'fr-CA'.
### Language Formatters
You can specify which part of a language you are interested in.
| Formatter | Description | Example Usage | Example Result |
|---------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------|----------------|
| \{O \| I \| I2 \| I3 \| E \| N \| W \| ID\} | Formats the language using<br>the language part tags.<br>\{O:[Text_Formatter](#text-formatters)\} = Language as provided by audible<br>\{I:[Text_Formatter](#text-formatters)\} = Two letter ISO code<br>\{I2:[Text_Formatter](#text-formatters)\} = Two letter ISO code<br>\{I3:[Text_Formatter](#text-formatters)\} = Three letter ISO code<br>\{E:[Text_Formatter](#text-formatters)\} = English name<br>\{N:[Text_Formatter](#text-formatters)\} = Native name - OS dependent<br>\{W:[Text_Formatter](#number-formatters)\} = Unique Windows code<br>\{ID:[Text_Formatter](#text-formatters)\} = Lang code<br><br>Formatter parts are optional and introduced by the colon. If specified the string will be used to format the part using the corresponding formatter.<br><br>Default is \{O\} | `<language[{I3:l} ({E})]>` | fra (French) |
| \{D\} | Display name interpreted by the current language settings.<br>To ensure output in a specific language the lang-code to use might be specified with a leading '@'.<br>Formatter part is also optional and introduced by the colon.<br>\{D@LANG:[Text_Formatter](#text-formatters)\} | `<language[{D@es}]>` | francés |
| Formatter | Description | Example Usage | Example Result |
|---------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------|----------------|
| \{O \| I \| I2 \| I3 \| E \| N \| W \| ID\} **†** | Formats the language using<br>the language part tags.<br>\{O:[Text_Formatter](#text-formatters)\} = Language as provided by audible<br>\{I:[Text_Formatter](#text-formatters)\} = Two letter ISO code<br>\{I2:[Text_Formatter](#text-formatters)\} = Two letter ISO code<br>\{I3:[Text_Formatter](#text-formatters)\} = Three letter ISO code<br>\{E:[Text_Formatter](#text-formatters)\} = English name<br>\{N:[Text_Formatter](#text-formatters)\} = Native name - OS dependent<br>\{W:[Text_Formatter](#number-formatters)\} = Unique Windows code<br>\{ID:[Text_Formatter](#text-formatters)\} = Lang code<br><br>Formatter parts are optional and introduced by the colon. If specified the string will be used to format the part using the corresponding formatter.<br><br>Default is \{O\} | `<language[{I3:l} ({E})]>` | fra (French) |
| \{D\} **†**,**‡** | Display name interpreted by the current language settings.<br>To ensure output in a specific language the lang-code to use might be specified with a leading '@'.<br>Formatter part is also optional and introduced by the colon.<br>\{D@LANG:[Text_Formatter](#text-formatters)\} | `<language[{D@es}]>` | francés |
**†** For further information on format templates, please refer to the [Format_Templates](#format-templates) section.
**‡** LANG may be any code from the [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) standard like `es` for Spanish, `en` for English, `de` for German, etc. or even a [IETF language tag](https://en.wikipedia.org/wiki/IETF_language_tag) like 'fr-CA'.
### Format Templates
Depending on which property is to be displayed, one or more placeholders can be used in a format template. The placeholders are defined in the form `{A}`:
`<first author[{F} {U}]>`
The format template must sometimes be enclosed in square brackets and sometimes in round brackets. In addition to placeholders, the format template may also contain arbitrary text. To prevent this text from being mistaken for a bracket at the end of the template or a placeholder, escapes can be used within the text:
* `\x` - Escapes the next character.
* `\\` - Escapes a backslash.
* `"text"` - encloses text that may contain special characters. To include a double quote in the text, escape it by doubling it: `"She said ""Hello"""` will output `She said "Hello"`.
* `'text'`- encloses text that may contain special characters. To include a single quote in the text, escape it by doubling it: `'It''s a test'` will output `It's a test`.
`<series[separator(,) format('{Series:' {N}\})]>`
Not all elements of a property are always present or have content. In this case, format templates would contain gaps after substitution. Groups of spaces are automatically merged. Other characters, however, remain unchanged. By doubling the curly brackets, you can specify text fragments before and after the placeholder, which are only used if the placeholder is replaced with content.
`<narrator[format({{F} }{\({M}') '}{L})]>``Neil Gaiman, Christopher (Evan) Welch`
### Checks