// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.Blazor.Components; using Microsoft.Blazor.Rendering; using System; using System.Collections.Generic; namespace Microsoft.Blazor.RenderTree { /// /// Provides methods for building a collection of entries. /// public class RenderTreeBuilder { private readonly Renderer _renderer; private readonly ArrayBuilder _entries = new ArrayBuilder(10); private readonly Stack _openElementIndices = new Stack(); private RenderTreeNodeType? _lastNonAttributeNodeType; /// /// Constructs an instance of . /// /// The associated . public RenderTreeBuilder(Renderer renderer) { _renderer = renderer ?? throw new ArgumentNullException(nameof(renderer)); } /// /// Appends a node representing an element, i.e., a container for other nodes. /// In order for the state to be valid, you must /// also call immediately after appending the /// new element's child nodes. /// /// An integer that represents the position of the instruction in the source code. /// A value representing the type of the element. public void OpenElement(int sequence, string elementName) { _openElementIndices.Push(_entries.Count); Append(RenderTreeNode.Element(sequence, elementName)); } /// /// Marks a previously appended element node as closed. Calls to this method /// must be balanced with calls to . /// public void CloseElement() { var indexOfEntryBeingClosed = _openElementIndices.Pop(); _entries.Buffer[indexOfEntryBeingClosed].CloseElement(_entries.Count - 1); } /// /// Appends a node representing text content. /// /// An integer that represents the position of the instruction in the source code. /// Content for the new text node. public void AddText(int sequence, string textContent) => Append(RenderTreeNode.Text(sequence, textContent)); /// /// Appends a node representing text content. /// /// An integer that represents the position of the instruction in the source code. /// Content for the new text node. public void AddText(int sequence, object textContent) => AddText(sequence, textContent?.ToString()); /// /// Appends a node representing a string-valued attribute. /// The attribute is associated with the most recently added element. /// /// An integer that represents the position of the instruction in the source code. /// The name of the attribute. /// The value of the attribute. public void AddAttribute(int sequence, string name, string value) { AssertCanAddAttribute(); Append(RenderTreeNode.Attribute(sequence, name, value)); } /// /// Appends a node representing an -valued attribute. /// The attribute is associated with the most recently added element. /// /// An integer that represents the position of the instruction in the source code. /// The name of the attribute. /// The value of the attribute. public void AddAttribute(int sequence, string name, UIEventHandler value) { AssertCanAddAttribute(); Append(RenderTreeNode.Attribute(sequence, name, value)); } /// /// Appends a node representing a string-valued attribute. /// The attribute is associated with the most recently added element. /// /// An integer that represents the position of the instruction in the source code. /// The name of the attribute. /// The value of the attribute. public void AddAttribute(int sequence, string name, object value) { AssertCanAddAttribute(); Append(RenderTreeNode.Attribute(sequence, name, value.ToString())); } /// /// Appends a node representing an attribute. /// The attribute is associated with the most recently added element. /// /// An integer that represents the position of the instruction in the source code. /// The name of the attribute. /// The value of the attribute. public void AddAttribute(int sequence, RenderTreeNode node) { if (node.NodeType != RenderTreeNodeType.Attribute) { throw new ArgumentException($"The {nameof(node.NodeType)} must be {RenderTreeNodeType.Attribute}."); } AssertCanAddAttribute(); node.SetSequence(sequence); Append(node); } /// /// Appends a node representing a child component. /// /// The type of the child component. /// An integer that represents the position of the instruction in the source code. public void AddComponent(int sequence) where TComponent: IComponent => Append(RenderTreeNode.ChildComponent(sequence)); private void AssertCanAddAttribute() { if (_lastNonAttributeNodeType != RenderTreeNodeType.Element && _lastNonAttributeNodeType != RenderTreeNodeType.Component) { throw new InvalidOperationException($"Attributes may only be added immediately after nodes of type {RenderTreeNodeType.Element} or {RenderTreeNodeType.Component}"); } } /// /// Clears the builder. /// public void Clear() { _entries.Clear(); _openElementIndices.Clear(); _lastNonAttributeNodeType = null; } /// /// Returns the values that have been appended. /// /// An array range of values. public ArrayRange GetNodes() => _entries.ToRange(); private void Append(in RenderTreeNode node) { _entries.Append(node); var nodeType = node.NodeType; if (nodeType != RenderTreeNodeType.Attribute) { _lastNonAttributeNodeType = node.NodeType; } } } }