// 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.Diagnostics; using System.Globalization; using YamlDotNet.Core; using YamlDotNet.Core.Events; namespace YamlDotNet.RepresentationModel { /// /// Represents an YAML document. /// public class YamlDocument { /// /// Gets or sets the root node. /// /// The root node. public YamlNode RootNode { get; private set; } /// /// Initializes a new instance of the class. /// public YamlDocument(YamlNode rootNode) { RootNode = rootNode; } /// /// Initializes a new instance of the class with a single scalar node. /// public YamlDocument(string rootNode) { RootNode = new YamlScalarNode(rootNode); } /// /// Initializes a new instance of the class. /// internal YamlDocument(IParser parser) { var state = new DocumentLoadingState(); parser.Consume(); while (!parser.TryConsume(out var _)) { Debug.Assert(RootNode == null); RootNode = YamlNode.ParseNode(parser, state); if (RootNode is YamlAliasNode) { throw new YamlException("A document cannot contain only an alias"); } } state.ResolveAliases(); // Throw should not happen unless the parser has a bug RootNode = RootNode ?? throw new ArgumentException("Atempted to parse an empty document"); } /// /// Visitor that assigns anchors to nodes that are referenced more than once. /// Existing anchors are preserved as much as possible. /// private class AnchorAssigningVisitor : YamlVisitorBase { private readonly HashSet existingAnchors = new HashSet(); /// /// Key: Node, Value: IsDuplicate /// private readonly Dictionary visitedNodes = new Dictionary(); public void AssignAnchors(YamlDocument document) { existingAnchors.Clear(); visitedNodes.Clear(); document.Accept(this); var random = new Random(); foreach (var visitedNode in visitedNodes) { if (visitedNode.Value) { AnchorName anchor; // If the existing anchor is not already used, we can have it if (!visitedNode.Key.Anchor.IsEmpty && !existingAnchors.Contains(visitedNode.Key.Anchor)) { anchor = visitedNode.Key.Anchor; } else { do { anchor = new AnchorName(random.Next().ToString(CultureInfo.InvariantCulture)); } while (existingAnchors.Contains(anchor)); } existingAnchors.Add(anchor); visitedNode.Key.Anchor = anchor; } } } /// /// Returns whether the visited node is a duplicate. /// private bool VisitNodeAndFindDuplicates(YamlNode node) { if (visitedNodes.TryGetValue(node, out var isDuplicate)) { if (!isDuplicate) { visitedNodes[node] = true; } return !isDuplicate; } else { visitedNodes.Add(node, false); return false; } } public override void Visit(YamlScalarNode scalar) { VisitNodeAndFindDuplicates(scalar); } public override void Visit(YamlMappingNode mapping) { if (!VisitNodeAndFindDuplicates(mapping)) { base.Visit(mapping); } } public override void Visit(YamlSequenceNode sequence) { if (!VisitNodeAndFindDuplicates(sequence)) { base.Visit(sequence); } } } private void AssignAnchors() { var visitor = new AnchorAssigningVisitor(); visitor.AssignAnchors(this); } internal void Save(IEmitter emitter, bool assignAnchors = true) { if (assignAnchors) { AssignAnchors(); } emitter.Emit(new DocumentStart()); RootNode.Save(emitter, new EmitterState()); emitter.Emit(new DocumentEnd(false)); } /// /// Accepts the specified visitor by calling the appropriate Visit method on it. /// /// /// A . /// public void Accept(IYamlVisitor visitor) { visitor.Visit(this); } /// /// Gets all nodes from the document. /// is thrown if an infinite recursion is detected. /// public IEnumerable AllNodes { get { return RootNode.AllNodes; } } } }