Skip to content

Commit

Permalink
Merge pull request #767 from LaXiS96/feature/yaml11_strings
Browse files Browse the repository at this point in the history
Option to quote YAML 1.1 strings
  • Loading branch information
EdwardCooke committed Jan 28, 2023
2 parents f4fdfd1 + 218e67b commit d079a3c
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 14 deletions.
23 changes: 23 additions & 0 deletions YamlDotNet.Test/Serialization/SerializationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2300,6 +2300,29 @@ public void StringsThatMatchKeywordsAreQuoted(string input)
Assert.Equal($"text: \"{input}\"{Environment.NewLine}", yaml);
}

public static IEnumerable<object[]> Yaml1_1SpecialStringsData = new[]
{
"-.inf", "-.Inf", "-.INF", "-0", "-0100_200", "-0b101", "-0x30", "-190:20:30", "-23", "-3.14",
"._", "._14", ".", ".0", ".1_4", ".14", ".3E-1", ".3e+3", ".inf", ".Inf",
".INF", ".nan", ".NaN", ".NAN", "+.inf", "+.Inf", "+.INF", "+0.3e+3", "+0",
"+0100_200", "+0b100", "+190:20:30", "+23", "+3.14", "~", "0.0", "0", "00", "001.23",
"0011", "010", "02_0", "07", "0b0", "0b100_101", "0o0", "0o10", "0o7", "0x0",
"0x10", "0x2_0", "0x42", "0xa", "100_000", "190:20:30.15", "190:20:30", "23", "3.", "3.14", "3.3e+3",
"85_230.15", "85.230_15e+03", "false", "False", "FALSE", "n", "N", "no", "No", "NO",
"null", "Null", "NULL", "off", "Off", "OFF", "on", "On", "ON", "true", "True", "TRUE",
"y", "Y", "yes", "Yes", "YES"
}.Select(v => new object[] { v }).ToList();

[Theory]
[MemberData(nameof(Yaml1_1SpecialStringsData))]
public void StringsThatMatchYaml1_1KeywordsAreQuoted(string input)
{
var serializer = new SerializerBuilder().WithQuotingNecessaryStrings(true).Build();
var o = new { text = input };
var yaml = serializer.Serialize(o);
Assert.Equal($"text: \"{input}\"{Environment.NewLine}", yaml);
}

[Fact]
public void KeysOnConcreteClassDontGetQuoted_TypeStringGetsQuoted()
{
Expand Down
63 changes: 52 additions & 11 deletions YamlDotNet/Serialization/EventEmitters/TypeAssigningEventEmitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,64 @@ public sealed class TypeAssigningEventEmitter : ChainedEventEmitter
private readonly bool requireTagWhenStaticAndActualTypesAreDifferent;
private readonly IDictionary<Type, TagName> tagMappings;
private readonly bool quoteNecessaryStrings;
private static readonly string IsSpecialStringValue_Regex =
private readonly Regex? isSpecialStringValue_Regex;
private static readonly string SpecialStrings_Pattern =
@"^("
+ @"null|Null|NULL|\~"
+ @"|true|True|TRUE|false|False|FALSE"
+ @"|[-+]?[0-9]+|0o[0-7]+"
+ @"|0x[0-9a-fA-F]+"
+ @"|[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?"
+ @"|[-+]?(\.inf|\.Inf|\.INF)"
+ @"|\.nan|\.NaN|\.NAN"
+ @"null|Null|NULL|\~"
+ @"|true|True|TRUE|false|False|FALSE"
+ @"|[-+]?[0-9]+" // int base 10
+ @"|0o[0-7]+" // int base 8
+ @"|0x[0-9a-fA-F]+" // int base 16
+ @"|[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?" // float number
+ @"|[-+]?(\.inf|\.Inf|\.INF)"
+ @"|\.nan|\.NaN|\.NAN"
+ @")$";

/// <summary>
/// This pattern matches strings that are special both in YAML 1.1 and 1.2
/// </summary>
private static readonly string CombinedYaml1_1SpecialStrings_Pattern =
@"^("
+ @"null|Null|NULL|\~"
+ @"|true|True|TRUE|false|False|FALSE"
+ @"|y|Y|yes|Yes|YES|n|N|no|No|NO"
+ @"|on|On|ON|off|Off|OFF"
+ @"|[-+]?0b[0-1_]+" // int base 2
+ @"|[-+]?0o?[0-7_]+" // int base 8 both with and without "o"
+ @"|[-+]?(0|[1-9][0-9_]*)" // int base 10
+ @"|[-+]?0x[0-9a-fA-F_]+" // int base 16
+ @"|[-+]?[1-9][0-9_]*(:[0-5]?[0-9])+" // int base 60
+ @"|[-+]?([0-9][0-9_]*)?\.[0-9_]*([eE][-+][0-9]+)?" // float base 10
+ @"|[-+]?[0-9][0-9_]*(:[0-5]?[0-9])+\.[0-9_]*" // float base 60
+ @"|[-+]?\.(inf|Inf|INF)"
+ @"|\.(nan|NaN|NAN)"
+ @")$";

public TypeAssigningEventEmitter(IEventEmitter nextEmitter, bool requireTagWhenStaticAndActualTypesAreDifferent, IDictionary<Type, TagName> tagMappings, bool quoteNecessaryStrings, bool quoteYaml1_1Strings)
: this(nextEmitter, requireTagWhenStaticAndActualTypesAreDifferent, tagMappings)
{
this.quoteNecessaryStrings = quoteNecessaryStrings;

var specialStringValuePattern = quoteYaml1_1Strings
? CombinedYaml1_1SpecialStrings_Pattern
: SpecialStrings_Pattern;
#if NET40
isSpecialStringValue_Regex = new Regex(specialStringValuePattern);
#else
isSpecialStringValue_Regex = new Regex(specialStringValuePattern, RegexOptions.Compiled);
#endif
}

public TypeAssigningEventEmitter(IEventEmitter nextEmitter, bool requireTagWhenStaticAndActualTypesAreDifferent, IDictionary<Type, TagName> tagMappings, bool quoteNecessaryStrings)
: this(nextEmitter, requireTagWhenStaticAndActualTypesAreDifferent, tagMappings)
{
this.quoteNecessaryStrings = quoteNecessaryStrings;

#if NET40
isSpecialStringValue_Regex = new Regex(SpecialStrings_Pattern);
#else
isSpecialStringValue_Regex = new Regex(SpecialStrings_Pattern, RegexOptions.Compiled);
#endif
}

public TypeAssigningEventEmitter(IEventEmitter nextEmitter, bool requireTagWhenStaticAndActualTypesAreDifferent, IDictionary<Type, TagName> tagMappings)
Expand Down Expand Up @@ -204,9 +247,7 @@ private bool IsSpecialStringValue(string value)
return true;
}

return Regex.IsMatch(
value,
IsSpecialStringValue_Regex);
return isSpecialStringValue_Regex?.IsMatch(value) ?? false;
}
}
}
9 changes: 6 additions & 3 deletions YamlDotNet/Serialization/SerializerBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public sealed class SerializerBuilder : BuilderSkeleton<SerializerBuilder>
private EmitterSettings emitterSettings = EmitterSettings.Default;
private DefaultValuesHandling defaultValuesHandlingConfiguration = DefaultValuesHandling.Preserve;
private bool quoteNecessaryStrings;
private bool quoteYaml1_1Strings;
private ITypeInspector? _baseTypeInspector = null;

public SerializerBuilder()
Expand Down Expand Up @@ -93,7 +94,7 @@ public SerializerBuilder()

eventEmitterFactories = new LazyComponentRegistrationList<IEventEmitter, IEventEmitter>
{
{ typeof(TypeAssigningEventEmitter), inner => new TypeAssigningEventEmitter(inner, false, tagMappings, quoteNecessaryStrings) }
{ typeof(TypeAssigningEventEmitter), inner => new TypeAssigningEventEmitter(inner, false, tagMappings, quoteNecessaryStrings, quoteYaml1_1Strings) }
};

objectGraphTraversalStrategyFactory = (typeInspector, typeResolver, typeConverters, maximumRecursion) => new FullObjectGraphTraversalStrategy(typeInspector, typeResolver, maximumRecursion, namingConvention);
Expand Down Expand Up @@ -124,9 +125,11 @@ public SerializerBuilder WithStaticContext(StaticContext context)
/// <summary>
/// Put double quotes around strings that need it, for example Null, True, False, a number. This should be called before any other "With" methods if you want this feature enabled.
/// </summary>
public SerializerBuilder WithQuotingNecessaryStrings()
/// <param name="quoteYaml1_1Strings">Also quote strings that are valid scalars in the YAML 1.1 specification (which includes boolean Yes/No/On/Off, base 60 numbers and more)</param>
public SerializerBuilder WithQuotingNecessaryStrings(bool quoteYaml1_1Strings = false)
{
quoteNecessaryStrings = true;
this.quoteYaml1_1Strings = quoteYaml1_1Strings;
return this;
}

Expand Down Expand Up @@ -282,7 +285,7 @@ public SerializerBuilder EnsureRoundtrip()
namingConvention,
settings
);
WithEventEmitter(inner => new TypeAssigningEventEmitter(inner, true, tagMappings, quoteNecessaryStrings), loc => loc.InsteadOf<TypeAssigningEventEmitter>());
WithEventEmitter(inner => new TypeAssigningEventEmitter(inner, true, tagMappings, quoteNecessaryStrings, quoteYaml1_1Strings), loc => loc.InsteadOf<TypeAssigningEventEmitter>());
return WithTypeInspector(inner => new ReadableAndWritablePropertiesTypeInspector(inner), loc => loc.OnBottom());
}

Expand Down

0 comments on commit d079a3c

Please sign in to comment.