#1762 add filter() to list properties

This commit is contained in:
Jo-Be-Co committed 2026-05-14 02:10:38 +02:00
1 parent a21bb8174d
commit f194032139
4 files changed
+115 -40

No files matched your search

@@ -11,13 +11,31 @@ internal partial interface IListFormat<TList> where TList : IListFormat<TList>
{
static IEnumerable<T> FilteredList<T>(string formatString, IEnumerable<T> items, CultureInfo? culture) where T : IFormattable
{
return Max(formatString, Slice(formatString, Unique(formatString, items, culture)));
return Max(formatString, Slice(formatString, Unique(formatString, Filter(formatString, items, culture), culture)));
static StringComparer GetStringComparer(CultureInfo? culture)
{
return StringComparer.Create(culture ?? CultureInfo.CurrentCulture, ignoreCase: true);
}
static IEnumerable<T> Filter(string formatString, IEnumerable<T> items, CultureInfo? culture)
{
if (!FilterRegex().TryMatch(formatString, out var filterMatch))
return items;
// read the format to apply on each item
var format = filterMatch.ResolveValue("format");
// use the operator to get a predicate function that compares the formatted item to the value specified in the filter
var predicate = CompareCondition.GetPredicate(filterMatch.Value, filterMatch.ResolveValue("op"));
// the value to compare the formatted item to. Might be a number or a quoted string.
CommonFormatters.TryGetLiteral(filterMatch.ResolveValue("value"), out var value);
// return only the items that match the predicate
return items.Where(FilterPredicate);
bool FilterPredicate(T n) => predicate(n.ToString(format, culture), value, culture);
}
static IEnumerable<T> Unique(string formatString, IEnumerable<T> items, CultureInfo? culture)
{
return UniqueRegex().TryMatch(formatString, out var uniqueMatch)
@@ -120,11 +138,36 @@ internal partial interface IListFormat<TList> where TList : IListFormat<TList>
[GeneratedRegex("""[Ss]eparator\((?<separator>(?:\\.|'[^']*'|"[^"]*"|[^\\'"])*?)\)""")]
private static partial Regex SeparatorRegex();
/// <summary> Count will substitute all list members with a single number equal to there count </summary>
/// <summary> Count will substitute all list members with a single number equal to their count </summary>
[GeneratedRegex("""[Cc]ount\((?<format>(?:\\.|'[^']*'|"[^"]*"|[^\\'"])*?)\)""")]
private static partial Regex CountRegex();
/// <summary> Unique will shrink the list to unique members after applying format to them </summary>
[GeneratedRegex("""[Uu]nique\((?<format>(?:\\.|'[^']*'|"[^"]*"|[^\\'"])*?)\)""")]
private static partial Regex UniqueRegex();
}
/// <summary> The filter will reduce the list, keeping only the items that match the specified criteria. </summary>
[GeneratedRegex("""
(?x) # option x: ignore all unescaped whitespace in pattern and allow comments starting with #
[Ff]ilter # name of the command 'filter' or 'Filter'
\( # details are enclosed in brackets
(?<format>(?: # the first part captured as <format> specifies how to format items before comparison
\\. # - '\' escapes always the next character.
| '[^']*' # - allow 'string' to be included in the format, with '' being an escaped ' character
| "[^"]*" # - allow "string" to be included in the format, with "" being an escaped " character
| [^\\'"] # - match any other character. This will not catch the operator at first. Because ...
) *? ) # With *? the pattern above tries not to consume the operator.
\s* # Separate the following operator with whitespace
(?<op> # capture operator in <op>
[\#!≡=≠~<>≤≥&∉∌∈∌⋂⊆⊇⊂⊃-]+ # allow a wide range of operators, all non alphanumeric so that no operator is confused as value
| :[a-z_]+: # allow :named: operators for readability, e.g. :contains:
) \s* # ignore space between operator and second property
(?<value> # the second operand is captured as <value> and is a quoted string encapsulated in either single or double quotes
'(?:[^']|'')*' # - allow 'string' to be included in the format, with '' being an escaped ' character
| "(?:[^"]|"")*" # - allow "string" to be included in the format, with "" being an escaped " character
| \d+ # - allow a number
) #
\s* \) # end the filter details with optional whitespace and a closing bracket
""")]
private static partial Regex FilterRegex();
}
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using AssertionHelper;
using FileManager.NamingTemplate;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -322,10 +321,33 @@ public class CommonFormattersTests
Assert.AreEqual(expected, unescaped);
}
[TestMethod]
[DataRow(null, false, null, "null")]
[DataRow("", false, null, "emptystring")]
[DataRow("42", true, 42, "number")]
[DataRow("\"only\" at start", false, null, "partly quoted")]
[DataRow("\"mismatched'", false, null, "wrong pair of quotes")]
[DataRow("\"simple string\"", true, "simple string", "simple quoted with double quotes")]
[DataRow("'simple string'", true, "simple string", "simple quoted with single quotes")]
[DataRow("\"string with \"\"escaped\"\" quotes\"", true, "string with \"escaped\" quotes", "quoted with embedded doubled quotes")]
[DataRow("'string with ''escaped'' quotes'", true, "string with 'escaped' quotes", "quoted with embedded doubled single quotes")]
[DataRow("\"string with 'single' quotes\"", true, "string with 'single' quotes", "quoted with embedded other quote type")]
[DataRow("\"string with ''doubled single'' and \\\"escaped double\\\" quotes\"", true, "string with ''doubled single'' and \\\"escaped double\\\" quotes", "quoted with embedded doubling")]
[DataRow(" \"string with whitespace\" ", true, "string with whitespace", "quoted with whitespace")]
[DataRow("\"\"", true, "", "empty quoted string")]
public void TryQuotedString_Various(string? value, bool expectedSuccess, object? expectedValue, string testDescription)
{
// WHEN
var result = CommonFormatters.TryGetLiteral(value, out var unQuotedValue);
// THEN
Assert.AreEqual(expectedSuccess, result, $"Failed for: {testDescription}");
Assert.AreEqual(expectedValue, unQuotedValue, $"Failed for: {testDescription}");
}
private class TestClass
{
public string? Author { get; set; }
public string? Title { get; set; }
public string? Author { get; init; }
public string? Title { get; init; }
}
}
@@ -400,6 +400,12 @@ namespace TemplatesTests
[DataRow("<author[sort(LF) slice(4..5)]>", "Charles E. Gannon, Emma Gannon")]
[DataRow("<author[sort(Lf) slice(4..5)]>", "Emma Gannon, Charles E. Gannon")]
[DataRow("<author[unique({L:1}) format({L})]>", "Browne, Gannon, Fetherolf, Montgomery, Van Doren")]
[DataRow("<author[filter({L:1} = 'B') count()]>", "2")]
[DataRow("<author[filter({F:1}~{L}~'J~B') format({L})]>", "Browne, Bon Jovi")] // match correct position of operator
[DataRow(@"<author[filter({F:1}\'{L:1} = 'J''B') format({L})]>", "Browne, Bon Jovi")] // allow quoted quotes
[DataRow("<author[filter(<'99') count()]>", "")] // strings with numerical operators are substituted by their length
[DataRow("<author[filter(<26) count()]>", "6")]
[DataRow("<author[filter({L:1} != 'B') format({L}) slice(2..3)]>", "Fetherolf, Montgomery")]
[DataRow("<author[count()]>", "7")]
[DataRow("<author[max(42) count()]>", "7")]
[DataRow("<author[max(2) count()]>", "2")]
@@ -873,6 +879,7 @@ namespace TemplatesTests
[DataRow("<tag[count(00)]>", "03")]
[DataRow("<tag[unique({S:3}) sort(s)]>", "Tag3")]
[DataRow("<tag[unique({S:3}) count()]>", "1")]
[DataRow("<tag[filter(~ '[2-5]')]>", "Tag2, Tag3")]
[DataRow("<tag [max(1)]>", "Tag1")]
[DataRow("<tag [slice(2..)]>", "Tag2, Tag3")]
[DataRow("<tag[sort(s)]>", "Tag3, Tag2, Tag1")]
+37 -34
View File
@@ -131,19 +131,20 @@ 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 formatters](#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 |
| unique(FMT) **†** | Reduce list members to a unique set. Entries are compared to each other after applying the given format. Duplicate entries (after format is applied) are removed, keeping the first occurrence. | `<tag[unique()]>`<hr>`<tag[unique({S:1L})`<br>`separator(;)]>` | Tag1, Tag2, Tag3<hr>tag1 |
| 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 |
| count(FMT) **‡** | Instead of returning some or all members of the list, print out the number of entries using the specified [format](#number-formatters). | `<tag[count()]>`<hr>`<tag[count(00)]>` | 5<hr>05 |
| 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 formatters](#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 |
| unique(FMT) **†** | Reduce list members to a unique set. Entries are compared to each other after applying the given format. Duplicate entries (after format is applied) are removed, keeping the first occurrence. | `<tag[unique()]>`<hr>`<tag[unique({S:1L})`<br>`separator(;)]>` | Tag1, Tag2, Tag3<hr>tag1 |
| 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 |
| count(FMT) **‡** | Instead of returning some or all members of the list, print out the number of entries using the specified [format](#number-formatters). | `<tag[count()]>`<hr>`<tag[count(00)]>` | 5<hr>05 |
| filter(FMT [CHECK](#checks) VALUE) **†** | Filter list entries based on a condition. Each item is first formatted using the specified text format (or the default format if FMT is omitted), then compared against VALUE using the specified [CHECK](#checks). Only matching entries are included in the output.<br><br>**Syntax:** `filter(FORMAT CHECK VALUE)` or `filter(CHECK VALUE)`<br>- `FORMAT`: Optional text format to apply to each entry (e.g., `{S}`, `{S:L}`, `{S:3}`); defaults to `{S}`<br>- `CHECK`: Comparison operator (e.g., `=`, `!=`, `~`)<br>- `VALUE`: The value to compare against | `<tag[filter({S} ~ '[3-9]')]>`<hr>`<tag[filter({S} != 'Ignore')]>`<hr>`<tag[filter(~ '[A-Z]')]` | Tag3, Tag4, Tag5<hr>Tag1, Tag2, Tag4, Tag5<hr>TagA, TagB, TagC |
**†** For further information on format templates, please refer to the [Format templates](#format-templates) section.
@@ -159,15 +160,16 @@ Text formatting can change length and case of the text. Use \<#\>, \<#\>\<case\>
### 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 |
| unique(FMT) **†** | Reduce list members to a unique set. Entries are compared to each other after applying the given format. Duplicate entries (after format is applied) are removed, keeping the first occurrence. | `<series[unique()]>`<hr>`<series[unique({N:L})`<br>`separator(; )]>` | Sherlock Holmes; Some Other Series<hr>sherlock holmes; some other series |
| 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 multi‑level 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 |
| count(FMT) **‡** | Instead of returning some or all members of the list, print out the number of series using the specified [format](#number-formatters). | `<series[count()]>`<hr>`<series[count(00)]>` | 2<hr>02 |
| 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 |
| unique(FMT) **†** | Reduce list members to a unique set. Entries are compared to each other after applying the given format. Duplicate entries (after format is applied) are removed, keeping the first occurrence. | `<series[unique()]>`<hr>`<series[unique({N:L})`<br>`separator(; )]>` | Sherlock Holmes; Some Other Series<hr>sherlock holmes; some other series |
| 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 multi‑level 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 |
| count(FMT) **‡** | Instead of returning some or all members of the list, print out the number of series using the specified [format](#number-formatters). | `<series[count()]>`<hr>`<series[count(00)]>` | 2<hr>02 |
| filter(FMT [CHECK](#checks) VALUE) **†** | Filter list entries based on a condition. Each series is first formatted using the specified [Series Format](#series-formatters) (or the default format if FMT is omitted), then compared against VALUE using the specified [CHECK](#checks). Only matching entries are included in the output.<br><br>**Syntax:** `filter(FORMAT CHECK VALUE)` or `filter(CHECK VALUE)`<br>- `FORMAT`: Optional series format to apply to each entry (e.g., `{N}`, `{N:L}`, `{#}`); defaults to `{N}`<br>- `CHECK`: Comparison operator (e.g., `=`, `!=`, `~`)<br>- `VALUE`: The value to compare against | `<series[filter({N} = 'Holmes')]>`<hr>`<series[filter(~ 'Sherlock')]` | Sherlock Holmes<hr>Sherlock Holmes |
**†** For further information on format templates, please refer to the [Format templates](#format-templates) section.
@@ -183,15 +185,16 @@ Text formatting can change length and case of the text. Use \<#\>, \<#\>\<case\>
### 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\_ |
| unique(FMT) **†** | Reduce list members to a unique set. Entries are compared to each other after applying the given format. Duplicate entries (after format is applied) are removed, keeping the first occurrence. | `<author[unique()]>`<hr>`<author[unique({L:L})`<br>`separator(; )]>` | Arthur Conan Doyle, Stephen Fry<hr>doyle; fry |
| 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 multi‑level 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 |
| count(FMT) **‡** | Instead of returning some or all members of the list, print out the number of names using the specified [format](#number-formatters). | `<author[count()]>`<hr>`<author[count(00)]>` | 2<hr>02 |
| 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\_ |
| unique(FMT) **†** | Reduce list members to a unique set. Entries are compared to each other after applying the given format. Duplicate entries (after format is applied) are removed, keeping the first occurrence. | `<author[unique()]>`<hr>`<author[unique({L:L})`<br>`separator(; )]>` | Arthur Conan Doyle, Stephen Fry<hr>doyle; fry |
| 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 multi‑level 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 |
| count(FMT) **‡** | Instead of returning some or all members of the list, print out the number of names using the specified [format](#number-formatters). | `<author[count()]>`<hr>`<author[count(00)]>` | 2<hr>02 |
| filter(FMT [CHECK](#checks) VALUE) **†** | Filter list entries based on a condition. Each person is first formatted using the specified name format (or the default format if FMT is omitted), then compared against VALUE using the specified [CHECK](#checks). Only matching entries are included in the output.<br><br>**Syntax:** `filter(FORMAT CHECK VALUE)` or `filter(CHECK VALUE)`<br>- `FORMAT`: Optional name format to apply to each entry (e.g., `{L}`, `{M}`, `{L}, {F}`); defaults to `{T} {F} {M} {L} {S}`<br>- `CHECK`: Comparison operator (e.g., `=`, `!=`, `~`)<br>- `VALUE`: The value to compare against | `<author[filter({L} = 'Doyle')]>`<hr>`<author[filter({F} ~ '^Stephen')]>`<hr>`<narrator[filter(~ 'Fry')]` | Conan Doyle<hr>Stephen Fry<hr>Stephen Fry |
**†** For further information on format templates, please refer to the [Format templates](#format-templates) section.
@@ -314,9 +317,9 @@ string literal `O'Reilly`, you can use either `'O''Reilly'` or `"O'Reilly"`.
| String Checks | Unicode Operator | Description | Examples |
|---------------|------------------|--------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------|
| = | | Matches if values are equal (case-insensitive) | \<is tag[=Tag1]-\> |
| != | | Matches if values are not equal (case-insensitive) | \<is first author[!=Arthur]-\><br>\<cmp "foo" != 'bar'-\> |
| ~ | | Matches if the first parameter matches the regular expression specified by the second parameter (case-insensitive) | \<is title[~(\[XYZ\]).*\\1]-\> |
| = | | Matches if values are equal (case-insensitive) | \<is tag[=Tag1]-\><br>\<author[filter({L} = 'Doyle')]> |
| != | | Matches if values are not equal (case-insensitive) | \<is first author[!=Arthur]-\><br>\<cmp "foo" != 'bar'-\><br>\<tag[filter({S} != 'Ignore')]> |
| ~ | | Matches if the first parameter matches the regular expression specified by the second parameter (case-insensitive) | \<is title[~(\[XYZ\]).*\\1]-\><br>\<tag[filter({S} ~ '[3-9]')]><br>\<narrator[filter({F} ~ '^Stephen')]> |
| Number Checks | Unicode Operator | Description | Examples |
|---------------|------------------|----------------------------------------------------------------|-----------------------------------------------------------|