diff --git a/YamlDotNet.Samples/DeserializeWithComment.cs b/YamlDotNet.Samples/DeserializeWithComment.cs new file mode 100644 index 00000000..5f04ddeb --- /dev/null +++ b/YamlDotNet.Samples/DeserializeWithComment.cs @@ -0,0 +1,84 @@ +// This file is part of YamlDotNet - A .NET library for YAML. +// Copyright (c) Antoine Aubry and contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using System.IO; +using Xunit.Abstractions; +using YamlDotNet.Core.ParsingComments; +using YamlDotNet.Samples.Helpers; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + + +namespace YamlDotNet.Samples +{ + public class DeserializeWithComment + { + private readonly ITestOutputHelper output; + + public DeserializeWithComment(ITestOutputHelper output) + { + this.output = output; + } + + [Sample( + DisplayName = "Deserializing with comments", + Description = "Shows how to process comments" + )] + public void Main() + { + var deserializer = new DeserializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .BuildWithCommentsDeserializer(); + var serializer = new SerializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .JsonCompatible() + .Build(); + + var content = WithInlineCommentsAndList; + + var parser = new ParserWithComments(new ScannerWithComments(new StringReader(content))); + var yamlObject = deserializer.Deserialize(parser); + + var json = serializer.Serialize(yamlObject); + + output.WriteLine(json); + Console.WriteLine(json); + + } + + private const string WithInlineCommentsAndList = +@"valuelist: + - string1 #{1st comment} + - string2 +# block comment + - string3 #{2nd comment} +simplevalue: 12 +objectlist: + - att1: 12 + att2: v1 + - att1: 13 + att2: v2 + - att1: 14 #3rd comment + att2: v3 #4th comment +"; + } +} diff --git a/YamlDotNet.Test/Serialization/SerializationTests.cs b/YamlDotNet.Test/Serialization/SerializationTests.cs index 28a502f9..41bad418 100644 --- a/YamlDotNet.Test/Serialization/SerializationTests.cs +++ b/YamlDotNet.Test/Serialization/SerializationTests.cs @@ -36,6 +36,7 @@ using Xunit; using YamlDotNet.Core; using YamlDotNet.Core.Events; +using YamlDotNet.Core.ParsingComments; using YamlDotNet.Serialization; using YamlDotNet.Serialization.Callbacks; using YamlDotNet.Serialization.NamingConventions; @@ -1647,6 +1648,42 @@ public void YamlConvertiblesAreAbleToEmitAndParseComments() Assert.Equal("The value", parsed.Value); } + [Fact] + public void ChildrenWithInlineComments() + { + var input = @"# document starts with comment +valuelist: + - string1 #{1st comment} + - string2 +# block comment + - string3 #{2nd comment} +simplevalue: 12 +objectlist: + - att1: 12 + att2: v1 + - att1: 13 + att2: v2 + - att1: 14 #3rd comment + att2: v3 #4th comment +"; + + var expected = @"{""valuelist"": [{""value"": ""string1"", ""comment"": ""{1st comment}""}, {""value"": ""string2"", ""comment"": """"}, {""value"": ""string3"", ""comment"": ""{2nd comment}""}], ""simplevalue"": {""value"": ""12"", ""comment"": """"}, ""objectlist"": [{""att1"": {""value"": ""12"", ""comment"": """"}, ""att2"": {""value"": ""v1"", ""comment"": """"}}, {""att1"": {""value"": ""13"", ""comment"": """"}, ""att2"": {""value"": ""v2"", ""comment"": """"}}, {""att1"": {""value"": ""14"", ""comment"": ""3rd comment""}, ""att2"": {""value"": ""v3"", ""comment"": ""4th comment""}}]} +"; + + var deserializer = new DeserializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .BuildWithCommentsDeserializer(); + var serializer = new SerializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .JsonCompatible() + .Build(); + var parser = new ParserWithComments(new ScannerWithComments(new StringReader(input))); + + var yamlObject = deserializer.Deserialize(parser); + var actual = serializer.Serialize(yamlObject); + + Assert.Equal(expected, actual); + } public class CommentWrapper : IYamlConvertible { public string Comment { get; set; } diff --git a/YamlDotNet/Core/Parser.cs b/YamlDotNet/Core/Parser.cs index df877ce7..1416acd9 100644 --- a/YamlDotNet/Core/Parser.cs +++ b/YamlDotNet/Core/Parser.cs @@ -55,6 +55,7 @@ public class Parser : IParser { pendingEvents.Enqueue(new Events.Comment(commentToken.Value, commentToken.IsInline, commentToken.Start, commentToken.End)); scanner.ConsumeCurrent(); + currentToken = scanner.Current; } else { diff --git a/YamlDotNet/Core/ParsingComments/DictionaryDeserializerWithComments.cs b/YamlDotNet/Core/ParsingComments/DictionaryDeserializerWithComments.cs new file mode 100644 index 00000000..320d9024 --- /dev/null +++ b/YamlDotNet/Core/ParsingComments/DictionaryDeserializerWithComments.cs @@ -0,0 +1,117 @@ +// This file is part of YamlDotNet - A .NET library for YAML. +// Copyright (c) Antoine Aubry and contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using YamlDotNet.Core.Events; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NodeDeserializers; + +namespace YamlDotNet.Core.ParsingComments +{ + public abstract class DictionaryDeserializerWithComments : DictionaryDeserializer + { + public DictionaryDeserializerWithComments(bool duplicateKeyChecking) : base(duplicateKeyChecking) + { + } + + protected override void Deserialize(Type tKey, Type tValue, IParser parser, Func nestedObjectDeserializer, IDictionary result) + { + parser.TryConsume(out var _); + var property = parser.Consume(); + while (!parser.TryConsume(out var _)) + { + ((IParserWithComments)parser).SkipFollowingComments(); + var key = nestedObjectDeserializer(parser, tKey); + ((IParserWithComments)parser).SkipFollowingComments(); + var originalValue = nestedObjectDeserializer(parser, tValue); + var valueWithComment = ParseStringWithComment((IParserWithComments)parser, originalValue); + var listValueWithComment = ParseListWithComment(valueWithComment); + var valuePromise = listValueWithComment as IValuePromise; + AddKeyValue(result, property, key, listValueWithComment, valuePromise); + } + } + + private static object? ParseListWithComment(object? value) + { + if (value is List list) + { + var newValue = new List(); + var stringValue = string.Empty; + var comment = string.Empty; + + foreach (var listItem in list) + { + if (listItem is string) + { + if (!string.IsNullOrEmpty(stringValue)) + { + newValue.Add(new ValueWithComment(stringValue, comment)); + } + stringValue = (string)listItem; + comment = string.Empty; + } + if (listItem is Comment && ((Comment)listItem).IsInline) + { + comment = ((Comment)listItem).Value; + } + } + if (!string.IsNullOrEmpty(stringValue)) + { + newValue.Add(new ValueWithComment(stringValue, comment)); + } + if (newValue.Any()) + { + if (newValue.Any(v => !string.IsNullOrEmpty(v.Comment))) + { + value = newValue; + } + else + { + value = newValue.Select(v => v.Value).ToList(); + } + } + } + + return value; + } + + private static object? ParseStringWithComment(IParserWithComments parser, object? value) + { + if (value is string) + { + var comment = string.Empty; + if (parser.TryConsume(out var valueComment)) + { + if (value is string && valueComment.IsInline) + { + comment = valueComment.Value; + } + parser.SkipFollowingComments(); + } + value = new ValueWithComment(value, comment); + } + return value; + } + } +} diff --git a/YamlDotNet/Core/ParsingComments/DictionaryNodeDeserializerWithComments.cs b/YamlDotNet/Core/ParsingComments/DictionaryNodeDeserializerWithComments.cs new file mode 100644 index 00000000..7110351b --- /dev/null +++ b/YamlDotNet/Core/ParsingComments/DictionaryNodeDeserializerWithComments.cs @@ -0,0 +1,81 @@ +// This file is part of YamlDotNet - A .NET library for YAML. +// Copyright (c) Antoine Aubry and contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + + +using System; +using System.Collections; +using System.Collections.Generic; +using YamlDotNet.Helpers; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.Utilities; + +namespace YamlDotNet.Core.ParsingComments +{ + public class DictionaryNodeDeserializerWithComments : DictionaryDeserializerWithComments, INodeDeserializer + { + private readonly IObjectFactory objectFactory; + + public DictionaryNodeDeserializerWithComments(IObjectFactory objectFactory, bool duplicateKeyChecking) : + base(duplicateKeyChecking) + { + this.objectFactory = objectFactory ?? throw new ArgumentNullException(nameof(objectFactory)); + } + + public bool Deserialize(IParser parser, Type expectedType, Func nestedObjectDeserializer, out object? value) + { + IDictionary? dictionary; + Type keyType, valueType; + var genericDictionaryType = ReflectionUtility.GetImplementedGenericInterface(expectedType, typeof(IDictionary<,>)); + if (genericDictionaryType != null) + { + var genericArguments = genericDictionaryType.GetGenericArguments(); + keyType = genericArguments[0]; + valueType = genericArguments[1]; + + value = objectFactory.Create(expectedType); + + dictionary = value as IDictionary; + if (dictionary == null) + { + // Uncommon case where a type implements IDictionary but not IDictionary + dictionary = (IDictionary?)Activator.CreateInstance(typeof(GenericDictionaryToNonGenericAdapter<,>).MakeGenericType(keyType, valueType), value); + } + } + else if (typeof(IDictionary).IsAssignableFrom(expectedType)) + { + keyType = typeof(object); + valueType = typeof(object); + + value = objectFactory.Create(expectedType); + dictionary = (IDictionary)value; + } + else + { + value = null; + return false; + } + + Deserialize(keyType, valueType, parser, nestedObjectDeserializer, dictionary!); + + return true; + } + } +} diff --git a/YamlDotNet/Core/ParsingComments/IParserWithComments.cs b/YamlDotNet/Core/ParsingComments/IParserWithComments.cs new file mode 100644 index 00000000..39124628 --- /dev/null +++ b/YamlDotNet/Core/ParsingComments/IParserWithComments.cs @@ -0,0 +1,37 @@ +// This file is part of YamlDotNet - A .NET library for YAML. +// Copyright (c) Antoine Aubry and contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +namespace YamlDotNet.Core.ParsingComments +{ + public interface IParserWithComments : IParser + { + + /// + /// Skips following comment events. + /// + void SkipFollowingComments(); + + /// + /// Gets the SkipComments value from its scanner. + /// + bool SkipComments { get; } + } +} diff --git a/YamlDotNet/Core/ParsingComments/IScannerWithComments.cs b/YamlDotNet/Core/ParsingComments/IScannerWithComments.cs new file mode 100644 index 00000000..45a86eff --- /dev/null +++ b/YamlDotNet/Core/ParsingComments/IScannerWithComments.cs @@ -0,0 +1,31 @@ +// This file is part of YamlDotNet - A .NET library for YAML. +// Copyright (c) Antoine Aubry and contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +namespace YamlDotNet.Core.ParsingComments +{ + public interface IScannerWithComments : IScanner + { + /// + /// Gets the SkipComments setting. + /// + bool SkipComments { get; } + } +} diff --git a/YamlDotNet/Core/ParsingComments/NodeCommentValueDeserializer.cs b/YamlDotNet/Core/ParsingComments/NodeCommentValueDeserializer.cs new file mode 100644 index 00000000..ec3a7109 --- /dev/null +++ b/YamlDotNet/Core/ParsingComments/NodeCommentValueDeserializer.cs @@ -0,0 +1,71 @@ +// This file is part of YamlDotNet - A .NET library for YAML. +// Copyright (c) Antoine Aubry and contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using System.Collections.Generic; +using YamlDotNet.Core.Events; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.Utilities; +using YamlDotNet.Serialization.ValueDeserializers; + +namespace YamlDotNet.Core.ParsingComments +{ + public sealed class NodeCommentValueDeserializer : IValueDeserializer + { + private readonly NodeValueDeserializer inner; + + public NodeCommentValueDeserializer(IList deserializers, IList typeResolvers, ITypeConverter typeConverter) + { + inner = new NodeValueDeserializer(deserializers, typeResolvers, typeConverter) + { + GetNodeEvent = this.GetNodeEvent + }; + } + + public object? DeserializeValue(IParser parser, Type expectedType, SerializerState state, IValueDeserializer nestedObjectDeserializer) + { + if (!(parser is ParserWithComments)) + { + throw new ArgumentException($"In {this.GetType().Name} the parser must be of type {typeof(ParserWithComments).Name} ParserWithComments"); + } + return inner.DeserializeValue(parser, expectedType, state, nestedObjectDeserializer); + } + + public NodeEvent? GetNodeEvent(IParser parser, Type expectedType) + { + parser.Accept(out var nodeEvent); + + if (nodeEvent == null + && !((IParserWithComments)parser).SkipComments + && !typeof(IYamlConvertible).IsAssignableFrom(expectedType)) + { + if (parser.Current is YamlDotNet.Core.Events.Comment cmt && cmt.IsInline) + { + return nodeEvent; + } + ((IParserWithComments)parser).SkipFollowingComments(); + parser.Accept(out nodeEvent); + } + + return nodeEvent; + } + } +} diff --git a/YamlDotNet/Core/ParsingComments/ParserWithComments.cs b/YamlDotNet/Core/ParsingComments/ParserWithComments.cs new file mode 100644 index 00000000..c3788409 --- /dev/null +++ b/YamlDotNet/Core/ParsingComments/ParserWithComments.cs @@ -0,0 +1,47 @@ +// This file is part of YamlDotNet - A .NET library for YAML. +// Copyright (c) Antoine Aubry and contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System.IO; + +namespace YamlDotNet.Core.ParsingComments +{ + public class ParserWithComments : Parser, IParserWithComments + { + public ParserWithComments(TextReader input) : base(input) + { + SkipComments = true; + } + + public ParserWithComments(IScannerWithComments scanner) : base(scanner) + { + SkipComments = scanner.SkipComments; + } + + public bool SkipComments { get; } + + public void SkipFollowingComments() + { + while (this.TryConsume(out var _)) + { + } + } + } +} diff --git a/YamlDotNet/Core/ParsingComments/ScannerWithComments.cs b/YamlDotNet/Core/ParsingComments/ScannerWithComments.cs new file mode 100644 index 00000000..77ece4b1 --- /dev/null +++ b/YamlDotNet/Core/ParsingComments/ScannerWithComments.cs @@ -0,0 +1,32 @@ +// This file is part of YamlDotNet - A .NET library for YAML. +// Copyright (c) Antoine Aubry and contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System.IO; + +namespace YamlDotNet.Core.ParsingComments +{ + public class ScannerWithComments : Scanner, IScannerWithComments + { + public ScannerWithComments(TextReader input) : base(input, false) + { + } + } +} diff --git a/YamlDotNet/Core/ParsingComments/StaticDictionaryNodeDeserializerWithComments.cs b/YamlDotNet/Core/ParsingComments/StaticDictionaryNodeDeserializerWithComments.cs new file mode 100644 index 00000000..fdad57c6 --- /dev/null +++ b/YamlDotNet/Core/ParsingComments/StaticDictionaryNodeDeserializerWithComments.cs @@ -0,0 +1,60 @@ +// This file is part of YamlDotNet - A .NET library for YAML. +// Copyright (c) Antoine Aubry and contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using System.Collections; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.ObjectFactories; + +namespace YamlDotNet.Core.ParsingComments +{ + public class StaticDictionaryNodeDeserializerWithComments : DictionaryDeserializerWithComments, INodeDeserializer + { + private readonly StaticObjectFactory _objectFactory; + + public StaticDictionaryNodeDeserializerWithComments(StaticObjectFactory objectFactory, bool duplicateKeyChecking) + : base(duplicateKeyChecking) + { + _objectFactory = objectFactory ?? throw new ArgumentNullException(nameof(objectFactory)); + } + + public bool Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object? value) + { + if (_objectFactory.IsDictionary(expectedType)) + { + var result = _objectFactory.Create(expectedType) as IDictionary; + if (result == null) + { + value = null; + return false; + } + var keyType = _objectFactory.GetKeyType(expectedType); + var valueType = _objectFactory.GetValueType(expectedType); + + value = result; + base.Deserialize(keyType, valueType, reader, nestedObjectDeserializer, result); + return true; + } + value = null; + return false; + } + } +} diff --git a/YamlDotNet/Core/ParsingComments/ValueWithComment.cs b/YamlDotNet/Core/ParsingComments/ValueWithComment.cs new file mode 100644 index 00000000..5721ae73 --- /dev/null +++ b/YamlDotNet/Core/ParsingComments/ValueWithComment.cs @@ -0,0 +1,35 @@ +// This file is part of YamlDotNet - A .NET library for YAML. +// Copyright (c) Antoine Aubry and contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +namespace YamlDotNet.Core.ParsingComments +{ + public class ValueWithComment + { + public ValueWithComment(object? value, string comment) + { + Value = value; + Comment = comment; + } + public object? Value { get; } + + public string Comment { get; } + } +} diff --git a/YamlDotNet/Serialization/DeserializerBuilder.cs b/YamlDotNet/Serialization/DeserializerBuilder.cs index d1faf54d..54dcdc79 100755 --- a/YamlDotNet/Serialization/DeserializerBuilder.cs +++ b/YamlDotNet/Serialization/DeserializerBuilder.cs @@ -25,6 +25,7 @@ using System.Diagnostics.CodeAnalysis; #endif using YamlDotNet.Core; +using YamlDotNet.Core.ParsingComments; using YamlDotNet.Serialization.BufferedDeserialization; using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.NodeDeserializers; @@ -458,5 +459,24 @@ public IValueDeserializer BuildValueDeserializer() ) ); } + + /// + /// Creates a new using the current configuration + /// and processing inline comments into the object structure. + /// + public IDeserializer BuildWithCommentsDeserializer() + { + WithNodeDeserializer(new DictionaryNodeDeserializerWithComments(objectFactory.Value, duplicateKeyChecking), s => s.InsteadOf()); + WithNodeDeserializer(new CommentNodeDeserializer(), s => s.Before()); + + var serializer = new AliasValueDeserializer( + new NodeCommentValueDeserializer( + nodeDeserializerFactories.BuildComponentList(), + nodeTypeResolverFactories.BuildComponentList(), + typeConverter + ) + ); + return Deserializer.FromValueDeserializer(serializer); + } } } diff --git a/YamlDotNet/Serialization/NodeDeserializers/CommentNodeDeserializer.cs b/YamlDotNet/Serialization/NodeDeserializers/CommentNodeDeserializer.cs new file mode 100644 index 00000000..79107489 --- /dev/null +++ b/YamlDotNet/Serialization/NodeDeserializers/CommentNodeDeserializer.cs @@ -0,0 +1,46 @@ +// This file is part of YamlDotNet - A .NET library for YAML. +// Copyright (c) Antoine Aubry and contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + + +using System; +using YamlDotNet.Core; +using YamlDotNet.Core.Events; + +namespace YamlDotNet.Serialization.NodeDeserializers +{ + public class CommentNodeDeserializer : INodeDeserializer + { + public bool Deserialize(IParser parser, Type expectedType, Func nestedObjectDeserializer, out object? value) + { + value = null; + if (parser.Accept(out var _)) + { + if (parser.TryConsume(out var comment)) + { + value = comment; + return true; + } + } + + return false; + } + } +} diff --git a/YamlDotNet/Serialization/NodeDeserializers/DictionaryDeserializer.cs b/YamlDotNet/Serialization/NodeDeserializers/DictionaryDeserializer.cs index 4bd45950..8a4c96b8 100644 --- a/YamlDotNet/Serialization/NodeDeserializers/DictionaryDeserializer.cs +++ b/YamlDotNet/Serialization/NodeDeserializers/DictionaryDeserializer.cs @@ -53,62 +53,67 @@ protected virtual void Deserialize(Type tKey, Type tValue, IParser parser, Func< var value = nestedObjectDeserializer(parser, tValue); var valuePromise = value as IValuePromise; - if (key is IValuePromise keyPromise) + AddKeyValue(result, property, key, value, valuePromise); + } + } + + protected void AddKeyValue(IDictionary result, MappingStart property, object? key, object? value, IValuePromise? valuePromise) + { + if (key is IValuePromise keyPromise) + { + if (valuePromise == null) { - if (valuePromise == null) - { - // Key is pending, value is known - keyPromise.ValueAvailable += v => result[v!] = value!; - } - else - { - // Both key and value are pending. We need to wait until both of them become available. - var hasFirstPart = false; + // Key is pending, value is known + keyPromise.ValueAvailable += v => result[v!] = value!; + } + else + { + // Both key and value are pending. We need to wait until both of them become available. + var hasFirstPart = false; - keyPromise.ValueAvailable += v => + keyPromise.ValueAvailable += v => + { + if (hasFirstPart) + { + TryAssign(result, v!, value!, property); + } + else { - if (hasFirstPart) - { - TryAssign(result, v!, value!, property); - } - else - { - key = v!; - hasFirstPart = true; - } - }; + key = v!; + hasFirstPart = true; + } + }; - valuePromise.ValueAvailable += v => + valuePromise.ValueAvailable += v => + { + if (hasFirstPart) { - if (hasFirstPart) - { - TryAssign(result, key, v!, property); - } - else - { - value = v; - hasFirstPart = true; - } - }; - } + TryAssign(result, key, v!, property); + } + else + { + value = v; + hasFirstPart = true; + } + }; } - else + } + else + { + if (key == null) { - if (key == null) - { - throw new ArgumentException("Empty key names are not supported yet.", "key"); - } + throw new ArgumentException("Empty key names are not supported yet.", "key"); + } - if (valuePromise == null) - { - // Happy path: both key and value are known - TryAssign(result, key, value!, property); - } - else - { - // Key is known, value is pending - valuePromise.ValueAvailable += v => result[key!] = v!; - } + if (valuePromise == null) + { + // Happy path: both key and value are known + TryAssign(result, key, value!, property); + } + else + { + // Key is known, value is pending + valuePromise.ValueAvailable += v => result[key!] = v!; } } } diff --git a/YamlDotNet/Serialization/StaticDeserializerBuilder.cs b/YamlDotNet/Serialization/StaticDeserializerBuilder.cs index 50229134..ff4d591c 100644 --- a/YamlDotNet/Serialization/StaticDeserializerBuilder.cs +++ b/YamlDotNet/Serialization/StaticDeserializerBuilder.cs @@ -25,6 +25,7 @@ using System.Diagnostics.CodeAnalysis; #endif using YamlDotNet.Core; +using YamlDotNet.Core.ParsingComments; using YamlDotNet.Serialization.BufferedDeserialization; using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.NodeDeserializers; @@ -417,5 +418,23 @@ public IValueDeserializer BuildValueDeserializer() ) ); } + /// + /// Creates a new using the current configuration + /// and processing inline comments into the object structure. + /// + public IDeserializer BuildWithCommentsDeserializer() + { + WithNodeDeserializer(new StaticDictionaryNodeDeserializerWithComments(factory, duplicateKeyChecking), s => s.InsteadOf()); + WithNodeDeserializer(new CommentNodeDeserializer(), s => s.Before()); + + var serializer = new AliasValueDeserializer( + new NodeCommentValueDeserializer( + nodeDeserializerFactories.BuildComponentList(), + nodeTypeResolverFactories.BuildComponentList(), + typeConverter + ) + ); + return Deserializer.FromValueDeserializer(serializer); + } } } diff --git a/YamlDotNet/Serialization/ValueDeserializers/NodeValueDeserializer.cs b/YamlDotNet/Serialization/ValueDeserializers/NodeValueDeserializer.cs index 2248d27b..1d242e18 100644 --- a/YamlDotNet/Serialization/ValueDeserializers/NodeValueDeserializer.cs +++ b/YamlDotNet/Serialization/ValueDeserializers/NodeValueDeserializer.cs @@ -38,11 +38,18 @@ public NodeValueDeserializer(IList deserializers, IList + { + parser.Accept(out var nodeEvent); + return nodeEvent; + }; } + public Func GetNodeEvent { get; set; } + public object? DeserializeValue(IParser parser, Type expectedType, SerializerState state, IValueDeserializer nestedObjectDeserializer) { - parser.Accept(out var nodeEvent); + var nodeEvent = GetNodeEvent(parser, expectedType); var nodeType = GetTypeFromEvent(nodeEvent, expectedType); try