This is a feature update, that is released upon feedback from the community.
Thread-safe mode is now enabled by default:
SmartSettings.IsThreadSafeMode == true
.
This has no impact on the API.
In case SmartFormat is exclusively utilized in a single-threaded context, SmartSettings.IsThreadSafeMode=false
should be considered for enhanced performance.
Static Smart
methods like Smart.Format(format, args) can now be called in an async
/ multi-threaded context.
The SmartFormatter
instance returned by Smart.Default
is flagged with the ThreadStatic
attribute.
Thanks to karljj1 for the PR.
Before v3.1.0 the format options for ListFormatter
could only contain literal text. Now Placeholder
s are allowed.
var args = new {
Names = new[] { "John", "Mary", "Amy" },
IsAnd = true, // true or false
Split = ", " // comma and space as list separator
};
_ = Smart.Format("{Names:list:{}|{Split}| {IsAnd:and|nor} }", args);
// Output for "IsAnd=true": "John, Mary and Amy"
// Output for "IsAnd=false": "John, Mary nor Amy"
You'll find a detailed description for all changes including code samples in the Wiki.
After implementing a zero allocation ValueStringBuilder
(#193, #228) and Object Pools (#229) for all classes which are frequently instantiated:
- Parsing is 10% faster with 50-80% less GC and memory allocation
- Formatting is up to 40% faster with 50% less GC and memory allocation
Sample of BenchmarkDotNet results under NetStandard2.1:
| Method | N | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|--------------- |------ |---------:|--------:|--------:|-----------:|----------:|------:|----------:|
SmartFormat v2.7.2 (only SingleThread)
| Format | 10000 | 223.9 ms | 1.48 ms | 1.38 ms | 21333.3333 | - | - | 172 MB |
SmartFormat v3.0.0
| SingleThread | 10000 | 108.2 ms | 0.52 ms | 0.49 ms | 3200.0000 | - | - | 26 MB |
| ThreadSafe | 10000 | 128.0 ms | 1.29 ms | 1.21 ms | 6000.0000 | - | - | 48 MB |
The main point about string.Format
compatibility is, how curly braces and colons are processed in the format string.
In most cases string.Format
compatibility does not bring any advantages.
With SmartSettings.StringFormatCompatibility = true
SmartFormat is fully compatible. The downside, however, ist that custom formatter extensions cannot be parsed and used with this setting.
With SmartSettings.StringFormatCompatibility = false
(default), all features of SmartFormat are available.
Reasoning: The distinction was necessary because of syntax conflicts between SmartFormat extensions and string.Format
. It brings a more concise and clear set of formatting rules and full string.Format
compatibility even in "edge cases".
-
C# like
nullable
notation allows to displayNullable<T>
types safely (#176). -
The SmartFormat notation is
"{SomeNullable?.Property}"
. IfSomeNullable
is null, the expression is evaluated asstring.Empty
."{SomeNullable.Property}"
would throw aFormattingException
.
Thread-Safe Caching (#229)
Object Pools, Reflection and other caches can operate in thread-safe mode.
(SmartFormatter
s and Parser
s still require one instance per thread.)
-
This was a limitation in v2. In v3, the
Parser
can parse any character as part of formatter options. This means e.g. no limitations forRegEx
expressions used inIsMatchFormatter
. Note: Special characters like(){}:\
must be escaped with\
. -
Literals may contain any Unicode characters (#166). Add unicode escape characters like
"\u1234"
. Thanks to @karljj1. -
Global support for Alignment (#174): In v2, Alignment of output values was limited to the
DefaultFormatter
. It's about the equivalent to e.g.string.Format("{0,10}")
. Alignment is now supported by allIFormatter
s. -
Full control about output characters, including whitespace.
Introduced bool ParserSettings.ParseInputAsHtml
.
The default is false
.
If true
, theParser
will parse all content inside <script> and <style> tags as LiteralText
. All other places may still contain Placeholder
s.
This is because <script> and <style> tags may contain curly or square braces, that interfere with the SmartFormat {Placeholder
}.
The character to split options and formats can be changed. This allows having the default split character |
as part of the output string.
Affects ChooseFormatter
, ConditionalFormatter
, IsMatchFormatter
, ListFormatter
, PluralLocalizationFormatter
, SubStringFormatter
.
Added a type cache which increases speed by factor 4. Thanks to @karljj1. (#155)
Speed increased by 10% with less GC pressure (#189).
The IsMatchFormatter
is a formatter with evaluation of regular expressions. It allows to control its output depending on a RegEx
match.
New: The formatter can output matching group values of a RegEx
(#245).
PluralLocalizationFormatter
(#209)
- Constructor with string argument for default language is removed.
- Property
DefaultTwoLetterISOLanguageName
is removed. CultureInfo.InvariantCulture
maps toCultureInfo.GetCultureInfo("en")
(#243).
Culture is now determined in this sequence (same as with LocalizationFormatter
):
a) Get the culture from the FormattingInfo.FormatterOptions
.
b) Get the culture from the IFormatProvider
argument (which may be a CultureInfo
) to SmartFormatter.Format(IFormatProvider, string, object?[])
c) The CultureInfo.CurrentUICulture
- Constructor with string argument for default language is removed.
- Property
DefaultTwoLetterISOLanguageName
is removed.
Culture is now determined in this sequence (same as with LocalizationFormatter
):
a) Get the culture from the FormattingInfo.FormatterOptions
.
b) Get the culture from the IFormatProvider
argument (which may be a CultureInfo
) to SmartFormatter.Format(IFormatProvider, string, object?[])
c) The CultureInfo.CurrentUICulture
Extended CommonLanguagesTimeTextInfo
, which now includes French, Spanish, Portuguese, Italian and German as new languages besides English out-of-the-box.
This notation - using formats as formatter options - was allowed in SmartFormat v2.x, but is now depreciated. It is still detected and working, as long as the format part is left empty.
var formatDepreciated = "{0:time(abbr hours noless)}";
This format string is recommended for SmartFormat v3 and later. It allows for including the language as an option to the TimeFormatter
:
// Without language option:
var formatRecommended = "{0:time:abbr hours noless:}";
// With language option:
var formatRecommended = "{0:time(en):abbr hours noless:}";
The formatter now accecpts a format argument with a nested Placeholder
that lets you format the result of the sub-string operation (#258).
Example: Convert the sub-string to lower-case:
Smart.Format("{0:substr(0,2):{ToLower}}", "ABC");
ChooseFormatter
#253
Modified ChooseFormatter
case-sensitivity for option strings. This modification is compatible with v2:
bool
andnull
as string: always case-insensitive- using
SmartSettings.CaseSensitivity
unless overridden withChooseFormatter.CaseSensitivity
- option strings comparison is culture-aware
The StringSource
takes over a part of the functionality, which has been implemented in ReflectionSource
in v2. Compared to reflection with caching, speed is 20% better at 25% less memory allocation. (#178, #216)
KeyValuePairSource
(#244)
The KeyValuePairSource
is a simple, cheap and performant way to create named placeholders.
Separation of JsonSource
into 2 ISource
extensions (#177, #201):
NewtonSoftJsonSource
SystemTextJsonSource
Both provide global variables that are stored in VariablesGroup
containers. These variables are not passed in as arguments when formatting a string. Instead, they are taken from one of these two registered (global) ISource
s. (#233)
Credits to Needle and their PersistentVariablesSource extension to SmartFormat.
In the context of Nullable Notation (see below), the NullFormatter
has been added. It outputs a custom string literal, if the variable is null
, else another literal (default is string.Empty
) or a nested Placeholder
. (#176, #199)
-
Added
LocalizationFormatter
to localize literals and placeholders (#207). -
Added
ILocalizationProvider
and a standard implemention asLocalizationProvider
, which handlesresx
resource files. A fallback culture can be set.LocalizationProvider
can search an unlimited number of defined resoures.
IFormatter
s have one single, unique name (#185).
In v2, IFormatter
s could have an unlimited number of names.
Any (custom) ISource
and IFormatter
can implement IInitializer
. Then, the SmartFormatter
will call Initialize(SmartFormatter smartFormatter)
of the extension, before adding it to the extension list (#180).
Added support for IList<object>
parameters to the SmartFormatter
(thanks to @karljj1) (#154)
Removed obsolete SmartObjects
(which have been replaced by ValueTuple
) (092b7b1
)
Introduced experimental bool ParserSettings.ParseInputAsHtml
.
The default is false
.
If true
, theParser
will parse all content inside <script> and <style> tags as LiteralText
. This is because <script> and <style> tags may contain curly or square braces, that interfere with the SmartFormat {Placeholder
}.
All other places may still contain Placeholder
s. (#203)
SmartFormat.NET
This is a package which references all packages below.
SmartFormat
SmartFormat is the core package. It comes with the most frequently used extensions built-in.
SmartFormat.Extensions.System.Text.Json
This package is a SmartFormat extension for formatting System.Text.Json
types as a source.
SmartFormat.Extensions.Newtonsoft.Json
This package is a SmartFormat extension for formatting Newtonsoft.Json
types as a source.
SmartFormat.Extensions.Xml
This package is a SmartFormat extension for reading and formatting System.Xml.Linq.XElement
s.
SmartFormat.Extensions.Time
This package is a SmartFormat extension for formatting System.DateTime
, System.DateTimeOffset
and System.TimeSpan
types.
- Hot fix: Newtonsoft.Json prior to version 13.0.1 is vulnerable. The minimum version of the package reference is now 13.0.1 Newtonsoft.Json prior to version 13.0.1 is vulnerable to Insecure Defaults due to improper handling of expressions with high nesting level that lead to StackOverFlow exception or high CPU and RAM usage. Exploiting this vulnerability results in Denial Of Service (DoS).
- Fixed:
ConditionalFormatter
processes unsigned numbers in arguments correctly. - Fixed:
JsonSource
: Corrected handling ofnull
values inNewtonsoft.Json
objects.
- Fixed: #179 DualFromZeroToTwo plural rule. Thanks to @OhSoGood
- Fixed: #211 Illegal placeholder characters that are not 8-bit, will no more throw unexpected
ThrowByteOverflowException
. Thanks to @bogatykh
- Fixed broken backward compatibilty introduced in v2.6.2 (issues referenced in #148, #147, #143).
- Fixed: Take an erroneous format string like
"this is {uncomplete"
(missing closing brace). Before v2.7.0 the parser handled{uncomplete
as aTextLiteral
, not as an erroneousPlaceholder
. - Fixed: Since v1.6.1 there was an undiscovered issue: If the
Parser
encountered aParsingError.TooManyClosingBraces
, this closing brace was simply "swallowed-up". This way, the result withParser.ErrorAction.MaintainTokens
differs from the original format string. From v2.7.0, the redundant closing brace is handled as aTextLiteral
. - Improved: For
ParsingError.TrailingOperatorsInSelector
andParsingError.InvalidCharactersInSelector
the causing character is now included in theException.Message
. - If you have issues formatting HTML with CSS and/or JavaScript included, please read the bullet-proof How-to in the Wiki
- Fix: Fully implemented all
Settings.ParseErrorAction
, see #143 - Thanks to Anders Jonsson
- Fixed #136
- Upgraded test project to netcoreapp3.1
- Enhanced SubString extension as described in PR142 - Thanks to Anders Jonsson
- Migrated project with Nullable Reference Types (NRT) enabled
Bugfix: ListFormatter
will now process IList
data sources only.
Supported frameworks now are:
- .Net Framework 4.6.1, 4.6.2, 4.7.2 and 4.8 (
System.Text.Json
is not supported for .Net Framework 4.5.x and thus had to be dropped) - .Net Standard 2.0 and 2.1
- Added
System.Text.Json.JsonElement
to the JsonSource extension.Newtonsoft.Json
is still included. - Added a demo version as a net5.0 WindowsDesktop App
- Supported framworks now are:
- .Net Framework 4.6.1, 4.7.2 and 4.8 (
System.Text.Json
is not supported for .Net Framework 4.5.x and thus had to be dropped) - .Net Standard 2.0 and 2.1
- .Net Framework 4.6.1, 4.7.2 and 4.8 (
- Updated the Wiki
Sources
- New: Added
ValueTupleSource
forValueTuple
s - Changed:
SmartObjects
andSmartObjectsSource
are depreciated in favor ofValueTupleSource
Settings
- Breaking Change: Internal string comparisons (i.e. for placeholder names) are no more culture-specific, but
Ordinal
orOrdinalIgnoreCase
respectively. See discussion under this issue. - Breaking Change: Default
ErrorAction
is nowThrowError
for parser and formatter, instead ofIgnore
Other
- Changed: Removed all members which were flagged obsolete since more than a year.
Fixed an issue with SmartObjects
TimeFormatter
- New: Supports DateTimeOffset as parameter
- Changed in v2.4.1:
DateTime
operations always use their Universal Time representation. (Before, in case aDateTime
had propertyKind
set toDateTimeKind.Unspecified
, the result of a comparison was ambiguous.) - CTOR TimeFormatter(languageCode) throws for not implemented languageCode
- CTOR TimeFormatter() is obsolete (redundant)
- Obsolete in TimeSpanUtility
- TimeSpan extension method Floor (redundant)
- TimeSpan extension method Ceiling (redundant)
ConditionalFormatter
- New: Supports DateTimeOffset as parameter
- Changed:
DateTime
operations always use their Universal Time representation. (Before, in case aDateTime
had propertyKind
set toKind.Unspecified
, the result of a comparison was ambiguous.)
Demo
- Updated with DateTimeOffset example
- Updated with TimeFormatter example
- Added SubStringFormatter thanks to arilani
- Improved code coverage in unit tests
- Updated dependencies
- As announced: Dropped support for .NET 4.0, which was released back in 2010.
- Support for JSON Objects as data source
- Added IsMatchFormatter thanks to ericpyle
- Fixes issue with unsigned integers #101
- This is the last version supporting .NET 4.0, which was released back in 2010.
- Fixes issue #101
- This version includes a breaking change:
- Before:
OnParsingFailure
event was invoked after each parsing error - Now:
OnParsingFailure
event is invoked after parsing is completedParsingErrorEventArgs
has a different signature. It now includes allParsingErrors
with all details that would be supplied during a parser exception. This also includes messages indicating where there the parse error occurred.
- Before:
- Fixes issue #94
- Characters Literals in Format Strings
- Improved working with several data sources: SmartObjects
- Changes in SmartSettings
- Fixed signing assemblies
- ReflectionSource now also gets members from base classes
- Added nesting and list tests
- Added coding samples
- Improved source xml docs
- Extended Wiki documentation for error handling and common pitfalls
- No more ambiguity between named formatters and string.Format formatting arguments: The parser checks whether the parsed name exists in one of the formatter extensions.
- SmartFormatter and Parser default to ErrorAction = ErrorAction.Ignore in release AND debug mode
- SmartFormatter has EventHandler OnFormattingFailure
- Parser has EventHandler OnParsingFailure
- Obsolete FormatItem.Text property removed (was replaced by RawText property some time ago)
- Assemblies are signed with a strong name key file
- Supported frameworks are .Net 4.0, .Net 4.5, .Net Core - dropped .Net 3.5 and earlier
- Added an icon to the nuget package
- Fixed issue with parsing named formatter options (#45)
- Minor changes to the Extensions API:
- Added fields to
ISelectorInfo
- Made
Text
obsolete in favor ofRawText
.
- Added
TemplateFormatter
. Allows you to register named templates, to be used within other templates.
Note: the TemplateFormatter extension is not a default extension, and must be added manually.
- Added "Nested Scopes" feature. This allows a nested template to
easily access outer scopes, without tricky workarounds.
For example: in"{Person: {Address: {City} {FirstName} } }"
,{City}
will come fromAddress
, but{FirstName}
will come fromPerson
.
This will work especially well with conditional formatting!
- Massive improvements to the Extension API. Code is cleaner and easier to use.
- Breaking changes: Any custom Extensions will need to be updated to match the new API.
Long story short, instead of 5 parameters passed toIFormatter.EvaluateFormat
, it now just gets a singleFormatterInfo
argument that contains all parameters. - Added the "choose" formatter, to eventually replace the ConditionalFormatter.
Hopefully it's self explanatory with these usage examples:
"{0:choose(1|2|3):one|two|three|default}"
works like a switch statement"{0:choose(null):nothing|{something}}"
can do null checking"{Gender:choose(Male|Female):his|her}"
works great with Enums
- Added "Named Formatters", which allows you to use a
specific formatter by specifying its name.
For example, "{0:plural: ___ }" will use the Plural formatter, and "{0:default: ___ }" will use the default formatter. - Added "Formatter Options", which allows you to specify options
for a named formatter. This will be used in the near future.
For example, "{0:name(options): ___ }"
- Added .NET v3.5 and .NET v4.0 builds
- Added "releases" folder to hold official releases
- Added
UseAlternativeBraces
method, so that templates can use alternative characters - Added
SmartFormatter.GetFormatterExtension
andGetSourceExtension
methods, which can be used to configure extensions - Added
DefaultTwoLetterISOLanguageName
properties to theTimeSpanFormatter
andPluralLocalizationFormatter
- Fixed
AddExtensions
to insert new extensions before existing ones (otherwise, it's pretty useless) (#17)
Converted from "CustomFormat" (VB.NET)