TemplateFormat
TemplateFormat determines text and parameter notation. See TemplateFormat.
Name | Description |
---|---|
Detect | Detect format |
Parameterless | No placeholders. |
BraceNumeric | "Hello {0}." |
BraceAlphaNumeric | "Hello {name}." |
Brace | "Hello {0}." or "Hello {name}." |
Percent | "Hello %1." |
BraceNumeric uses numeric parameters.
# Configuration
TemplateFormat: BraceNumeric
English:
- Culture: en
Items:
- Key: Namespace.CatsDogs
Text: "{0} cats and {1} dogs"
BraceAlphaNumeric allows text and numeric parameter names.
# Configuration
TemplateFormat: BraceAlphaNumeric
PluralRules: Unicode.CLDR41
English:
- Culture: en
Items:
- Key: Namespace.CatsDogs
Text: "{cats} cats and {dogs} dogs"
Note that parameter name in Plurals must correlate with parameter name in Text part.
# Configuration
TemplateFormat: BraceAlphaNumeric
PluralRules: Unicode.CLDR41
English:
- Culture: en
Items:
- Key: Namespace.CatsDogs
Cases:
- Text: "a cat and a dog"
Plurals: "cats:cardinal:one,dogs:cardinal:one"
- Text: "{cats} cats and a dog"
Plurals: "cats:cardinal:other,dogs:cardinal:one"
- Text: "a cat and {dogs} dogs"
Plurals: "cats:cardinal:one,dogs:cardinal:other"
- Text: "{cats} cats and {dogs} dogs"
Plurals: "cats:cardinal:other,dogs:cardinal:other"
Percent format uses %1 placeholder format where indices start at 1.
# Configuration
TemplateFormat: Percent
PluralRules: Unicode.CLDR41
English:
- Culture: en
Items:
- Key: Namespace.CatsDogs
Cases:
- Text: "a cat and a dog"
Plurals: "1:cardinal:one,2:cardinal:one"
- Text: "%1 cats and a dog"
Plurals: "1:cardinal:other,2:cardinal:one"
- Text: "a cat and %2 dogs"
Plurals: "1:cardinal:one,2:cardinal:other"
- Text: "%1 cats and %2 dogs"
Plurals: "1:cardinal:other,2:cardinal:other"
Parameterless template format is for parameterless texts.
# Configuration
PluralRules: Unicode.CLDR41
English:
- Culture: en
Items:
- Key: Namespace.Welcome
TemplateFormat: Parameterless
Text: "Welcome."
Detect template format detects best matching format.
# Configuration
PluralRules: Unicode.CLDR41
TemplateFormat: Detect
English:
- Culture: en
Items:
- Key: Namespace.Apples
Text: "You've got {0} apple(s)."
Plurals: "0:cardinal:other"
.AddTemplateFormat(templateFormat) adds a template format to localization.
// Get template format
ITemplateFormat dash = DashTemplateFormat.Instance;
// Create localization
ILocalization localization = new Localization()
.AddTemplateFormat(dash)
.AddLines(new LocalizationReaderYaml.File("templateformat/templateformat6.l.yaml"));
// Get text
ITemplateFormatPrintable text = localization.LocalizableTextCached["Namespace.Apples"];
// Print
WriteLine(text.Print(CultureInfo.InvariantCulture, new object[] { 3 })); // You've got 3 apple(s).
The added TemplateFormat "Dash" is now accessible from localization files.
# Configuration
PluralRules: Unicode.CLDR41
TemplateFormat: Dash
InvariantCulture:
- Culture: ""
Items:
- Key: Namespace.Apples
Text: "You've got #0# apple(s)."
It is also accessible with TemplateFormat "Detect".
# Configuration
PluralRules: Unicode.CLDR41
TemplateFormat: Detect
InvariantCulture:
- Culture: ""
Items:
- Key: Namespace.Apples
Text: "You've got #0# apple(s)."
DashTemplateFormat.cs
using System;
using Avalanche.Template;
using Avalanche.Template.Internal;
using Avalanche.Tokenizer;
using Avalanche.Utilities;
/// <summary>String format that uses dash placeholders "#parameter#". </summary>
public class DashTemplateFormat : TemplateFormatBase.TextIsBreakdown
{
/// <summary>Singleton</summary>
static DashTemplateFormat instance = new DashTemplateFormat();
/// <summary>Singleton</summary>
public static DashTemplateFormat Instance => instance;
/// <summary>'#'</summary>
protected static readonly ConstantTokenizer dashTokenizer = new ConstantTokenizer("#");
/// <summary>'#name#'</summary>
protected static readonly PlaceholderTokenizer placeholderTokenizer = new PlaceholderTokenizer(dashTokenizer, CharTokenizer.AlphaNumeric, IntegerTokenizer.Instance, new UntilTokenizer(dashTokenizer, false, null), dashTokenizer);
/// <summary>Singleton</summary>
static TemplateTextTokenizer tokenizer = new TemplateTextTokenizer(new UntilTokenizer(dashTokenizer, false, null), placeholderTokenizer, MalformedTokenizer.Instance);
/// <summary>Singleton</summary>
public static TemplateTextTokenizer Tokenizer => tokenizer;
/// <summary></summary>
public DashTemplateFormat() : base("Dash") { }
/// <summary>Breakdown <paramref name="text"/> into format string.</summary>
protected override bool TryBreakdown(string text, out ITemplateBreakdown result)
{
// Handle null
if (text == null)
{
TemplateBreakdown formatString_ = new TemplateBreakdown();
formatString_.TemplateFormat = this;
result = formatString_;
return true;
}
// Create format string
TemplateBreakdown templateBreakdown = new TemplateBreakdown();
templateBreakdown.Text = text;
// Tokenize
if (!tokenizer.TryTake(text, out CompositeToken formatStringToken)) { result = default!; return false; }
// Place parts here
templateBreakdown.Parts = new ITemplatePart[formatStringToken.Children.Length];
StructList6<ITemplatePlaceholderPart> placeholders = new();
// Convert tokens to parts
for (int i = 0; i < formatStringToken.Children.Length; i++)
{
// Get token
IToken token = formatStringToken.Children[i];
// Place part here
ITemplatePart part;
// Convert token to part
switch (token)
{
case TextToken textToken:
part = new TemplateTextPart().SetBreakdown(templateBreakdown).SetTexts(token.Memory).SetReadOnly();
break;
case PlaceholderToken placeholderToken:
var parameterPart = placeholderToken.Parameter == null ? null : new TemplateParameterPart().SetBreakdown(templateBreakdown).SetTexts(placeholderToken.Parameter.Memory);
var alignmentPart = placeholderToken.Alignment == null ? null : new TemplateAlignmentPart().SetBreakdown(templateBreakdown).SetTexts(placeholderToken.Alignment.Memory).SetReadOnly();
var formattingPart = placeholderToken.Formatting == null ? null : new TemplateFormattingPart().SetBreakdown(templateBreakdown).SetTexts(placeholderToken.Formatting.Memory).SetReadOnly();
var part2 = new TemplatePlaceholderPart
{
TemplateBreakdown = templateBreakdown,
Parameter = parameterPart!,
Alignment = alignmentPart,
Formatting = formattingPart,
}.SetEscaped(token.Memory).SetReadOnly();
part = part2;
placeholders.Add(part2);
break;
default:
part = new TemplateMalformedPart { TemplateBreakdown = templateBreakdown }.SetTexts(token.Memory).SetReadOnly();
break;
}
//
templateBreakdown.Parts[i] = part;
}
//
StructList8<ReadOnlyMemory<char>> parameterNames = new(MemoryEqualityComparer<char>.Instance);
// Assign parameter indices
foreach (ITemplatePart part in templateBreakdown.Parts)
{
// Cast
if (part is not ITemplatePlaceholderPart placeholder) continue;
// Get parameter name
ReadOnlyMemory<char> parameterName = placeholder.Parameter.Unescaped;
// Search
int parameterIx = parameterNames.IndexOf(parameterName);
// Add new
if (parameterIx < 0) { parameterIx = parameterNames.Count; parameterNames.Add(parameterName); }
// Assign
placeholder.Parameter.ParameterIndex = parameterIx;
// Set read-only
((IReadOnly)placeholder.Parameter).SetReadOnly();
}
// Assign template format
templateBreakdown.TemplateFormat = this;
// Assign result
templateBreakdown.SetReadOnly();
result = templateBreakdown;
return true;
}
/// <summary>Put format string together.</summary>
protected override bool TryAssemble(ITemplateBreakdown templateBreakdown, out string result)
{
// Got null
if (templateBreakdown == null) { result = null!; return false; }
// Return original string
if (templateBreakdown.TemplateFormat == this) { result = templateBreakdown.Text; return true; }
// Place length here
int length = 0;
// Calculate length
foreach (ITemplatePart part in templateBreakdown.Parts)
{
if (part is ITemplateTextPart text)
{
// Estimate re-escaped length
length += text.Length();
}
else if (part is ITemplatePlaceholderPart placeholder)
{
// '#' and '#'
length += 2;
// parameter
if (placeholder.Parameter != null)
{
length += placeholder.Parameter.Length();
}
// alignment
if (placeholder.Alignment != null)
{
length += placeholder.Alignment.Length() + 1;
}
// formatting
if (placeholder.Formatting != null)
{
length += placeholder.Formatting.Length() + 1;
}
}
else
{
// Append as is
length += part.Length();
}
}
// Allocate
Span<char> buf = length < 256 ? stackalloc char[length] : new char[length];
ReadOnlySpan<char> buf2 = buf;
// Append parts
foreach (ITemplatePart part in templateBreakdown.Parts)
{
if (part is ITemplateTextPart text)
{
// Use as is
text.Escaped.Span.CopyTo(buf); buf = buf.Slice(text.Escaped.Length);
}
else if (part is ITemplatePlaceholderPart placeholder)
{
// '#' and '#'
buf[0] = '#'; buf = buf.Slice(1);
// parameter
if (placeholder.Parameter != null)
{
placeholder.Parameter.Escaped.Span.CopyTo(buf); buf = buf.Slice(placeholder.Parameter.Escaped.Length);
}
// alignment
if (placeholder.Alignment != null)
{
// ','
buf[0] = ','; buf = buf.Slice(1);
placeholder.Alignment.Escaped.Span.CopyTo(buf);
buf = buf.Slice(placeholder.Alignment.Escaped.Length);
}
// formatting
if (placeholder.Formatting != null)
{
// ':'
buf[0] = ':'; buf = buf.Slice(1);
placeholder.Formatting.Escaped.Span.CopyTo(buf);
buf = buf.Slice(placeholder.Formatting.Escaped.Length);
}
// '#'
buf[0] = '#'; buf = buf.Slice(1);
}
else
{
// Append as is
part.Escaped.Span.CopyTo(buf);
buf = buf.Slice(part.Escaped.Length);
}
}
// Create string
result = new string(buf2);
// Ok
return true;
}
}