// 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 System.Text; using YamlDotNet.Core; using YamlDotNet.Core.Events; using YamlDotNet.Helpers; using YamlDotNet.Serialization; using static YamlDotNet.Core.HashCode; namespace YamlDotNet.RepresentationModel { /// /// Represents a mapping node in the YAML document. /// public sealed class YamlMappingNode : YamlNode, IEnumerable>, IYamlConvertible { private readonly IOrderedDictionary children = new OrderedDictionary(); /// /// Gets the children of the current node. /// /// The children. public IOrderedDictionary Children { get { return children; } } /// /// Gets or sets the style of the node. /// /// The style. public MappingStyle Style { get; set; } /// /// Initializes a new instance of the class. /// internal YamlMappingNode(IParser parser, DocumentLoadingState state) { Load(parser, state); } private void Load(IParser parser, DocumentLoadingState state) { var mapping = parser.Consume(); Load(mapping, state); Style = mapping.Style; var hasUnresolvedAliases = false; while (!parser.TryConsume(out var _)) { var key = ParseNode(parser, state); var value = ParseNode(parser, state); try { children.Add(key, value); } catch (ArgumentException err) { throw new YamlException(key.Start, key.End, "Duplicate key", err); } hasUnresolvedAliases |= key is YamlAliasNode || value is YamlAliasNode; } if (hasUnresolvedAliases) { state.AddNodeWithUnresolvedAliases(this); } } /// /// Initializes a new instance of the class. /// public YamlMappingNode() { } /// /// Initializes a new instance of the class. /// public YamlMappingNode(params KeyValuePair[] children) : this((IEnumerable>)children) { } /// /// Initializes a new instance of the class. /// public YamlMappingNode(IEnumerable> children) { foreach (var child in children) { this.children.Add(child); } } /// /// Initializes a new instance of the class. /// /// A sequence of where even elements are keys and odd elements are values. public YamlMappingNode(params YamlNode[] children) : this((IEnumerable)children) { } /// /// Initializes a new instance of the class. /// /// A sequence of where even elements are keys and odd elements are values. public YamlMappingNode(IEnumerable children) { using var enumerator = children.GetEnumerator(); while (enumerator.MoveNext()) { var key = enumerator.Current; if (!enumerator.MoveNext()) { throw new ArgumentException("When constructing a mapping node with a sequence, the number of elements of the sequence must be even."); } Add(key, enumerator.Current); } } /// /// Adds the specified mapping to the collection. /// /// The key node. /// The value node. public void Add(YamlNode key, YamlNode value) { children.Add(key, value); } /// /// Adds the specified mapping to the collection. /// /// The key node. /// The value node. public void Add(string key, YamlNode value) { children.Add(new YamlScalarNode(key), value); } /// /// Adds the specified mapping to the collection. /// /// The key node. /// The value node. public void Add(YamlNode key, string value) { children.Add(key, new YamlScalarNode(value)); } /// /// Adds the specified mapping to the collection. /// /// The key node. /// The value node. public void Add(string key, string value) { children.Add(new YamlScalarNode(key), new YamlScalarNode(value)); } /// /// Resolves the aliases that could not be resolved when the node was created. /// /// The state of the document. internal override void ResolveAliases(DocumentLoadingState state) { Dictionary? keysToUpdate = null; Dictionary? valuesToUpdate = null; foreach (var entry in children) { if (entry.Key is YamlAliasNode) { if (keysToUpdate == null) { keysToUpdate = new Dictionary(); } // TODO: The representation model should be redesigned, because here the anchor could be null but that would be invalid YAML keysToUpdate.Add(entry.Key, state.GetNode(entry.Key.Anchor!, entry.Key.Start, entry.Key.End)); } if (entry.Value is YamlAliasNode) { if (valuesToUpdate == null) { valuesToUpdate = new Dictionary(); } // TODO: The representation model should be redesigned, because here the anchor could be null but that would be invalid YAML valuesToUpdate.Add(entry.Key, state.GetNode(entry.Value.Anchor!, entry.Value.Start, entry.Value.End)); } } if (valuesToUpdate != null) { foreach (var entry in valuesToUpdate) { children[entry.Key] = entry.Value; } } if (keysToUpdate != null) { foreach (var entry in keysToUpdate) { var value = children[entry.Key]; children.Remove(entry.Key); children.Add(entry.Value, value); } } } /// /// Saves the current node to the specified emitter. /// /// The emitter where the node is to be saved. /// The state. internal override void Emit(IEmitter emitter, EmitterState state) { emitter.Emit(new MappingStart(Anchor, Tag, true, Style)); foreach (var entry in children) { entry.Key.Save(emitter, state); entry.Value.Save(emitter, state); } emitter.Emit(new MappingEnd()); } /// /// Accepts the specified visitor by calling the appropriate Visit method on it. /// /// /// A . /// public override void Accept(IYamlVisitor visitor) { visitor.Visit(this); } /// public override bool Equals(object? obj) { var other = obj as YamlMappingNode; var areEqual = other != null && Equals(Tag, other.Tag) && children.Count == other.children.Count; if (!areEqual) { return false; } foreach (var entry in children) { if (!other!.children.TryGetValue(entry.Key, out var otherNode) || !Equals(entry.Value, otherNode)) { return false; } } return true; } /// /// Serves as a hash function for a particular type. /// /// /// A hash code for the current . /// public override int GetHashCode() { var hashCode = base.GetHashCode(); foreach (var entry in children) { hashCode = CombineHashCodes(hashCode, entry.Key); hashCode = CombineHashCodes(hashCode, entry.Value); } return hashCode; } /// /// Recursively enumerates all the nodes from the document, starting on the current node, /// and throwing /// if is reached. /// internal override IEnumerable SafeAllNodes(RecursionLevel level) { level.Increment(); yield return this; foreach (var child in children) { foreach (var node in child.Key.SafeAllNodes(level)) { yield return node; } foreach (var node in child.Value.SafeAllNodes(level)) { yield return node; } } level.Decrement(); } /// /// Gets the type of node. /// public override YamlNodeType NodeType { get { return YamlNodeType.Mapping; } } /// /// Returns a that represents this instance. /// /// /// A that represents this instance. /// internal override string ToString(RecursionLevel level) { if (!level.TryIncrement()) { return MaximumRecursionLevelReachedToStringValue; } var text = new StringBuilder("{ "); foreach (var child in children) { if (text.Length > 2) { text.Append(", "); } text.Append("{ ").Append(child.Key.ToString(level)).Append(", ").Append(child.Value.ToString(level)).Append(" }"); } text.Append(" }"); level.Decrement(); return text.ToString(); } #region IEnumerable> Members /// public IEnumerator> GetEnumerator() { return children.GetEnumerator(); } #endregion #region IEnumerable Members System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion void IYamlConvertible.Read(IParser parser, Type expectedType, ObjectDeserializer nestedObjectDeserializer) { Load(parser, new DocumentLoadingState()); } void IYamlConvertible.Write(IEmitter emitter, ObjectSerializer nestedObjectSerializer) { Emit(emitter, new EmitterState()); } /// /// Creates a containing a key-value pair for each property of the specified object. /// public static YamlMappingNode FromObject(object mapping) { if (mapping == null) { throw new ArgumentNullException(nameof(mapping)); } var result = new YamlMappingNode(); foreach (var property in mapping.GetType().GetPublicProperties()) { // CanRead == true => GetGetMethod() != null if (property.CanRead && property.GetGetMethod(false)!.GetParameters().Length == 0) { var value = property.GetValue(mapping, null); if (!(value is YamlNode valueNode)) { var valueAsString = Convert.ToString(value); valueNode = valueAsString ?? string.Empty; } result.Add(property.Name, valueNode); } } return result; } } }