437 lines
22 KiB
C#
437 lines
22 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using JetBrains.Annotations;
|
|
|
|
namespace SharpNBT
|
|
{
|
|
/// <summary>
|
|
/// Provides a mechanism for easily building a tree of NBT objects by handling the intermediate step of creating tags, allowing the direct adding of their
|
|
/// equivalent values.
|
|
/// <para/>
|
|
/// All methods return the <see cref="TagBuilder"/> instance itself, allowing for easily chaining calls to build a document.
|
|
/// </summary>
|
|
[PublicAPI]
|
|
public class TagBuilder
|
|
{
|
|
private readonly CompoundTag root;
|
|
private readonly Stack<TagContainer> tree;
|
|
|
|
/// <summary>
|
|
/// Gets the zero-based depth of the current node, indicating how deeply nested it is within other tags.
|
|
/// </summary>
|
|
/// <remarks>The implicit top-level <see cref="CompoundTag"/> is not factored into this value.</remarks>
|
|
public int Depth => tree.Count - 1;
|
|
|
|
/// <summary>
|
|
/// Creates a new instance of the <see cref="TagBuilder"/> class, optionally with a <paramref name="name"/> to assign the top-level
|
|
/// <see cref="CompoundTag"/> of the final result.
|
|
/// </summary>
|
|
/// <param name="name"></param>
|
|
public TagBuilder([CanBeNull] string name = null)
|
|
{
|
|
root = new CompoundTag(name);
|
|
tree = new Stack<TagContainer>();
|
|
tree.Push(root);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a new <see cref="ByteTag"/> with the specified <paramref name="name"/> and <paramref name="value"/> to the tree at the current depth.
|
|
/// </summary>
|
|
/// <param name="name">The name of the node to add.</param>
|
|
/// <param name="value">The value of the tag.</param>
|
|
/// <returns>Returns this <see cref="TagBuilder"/> instance for chaining.</returns>
|
|
public TagBuilder AddByte([CanBeNull] string name, byte value) => AddTag(new ByteTag(name, value));
|
|
|
|
/// <inheritdoc cref="AddByte(string,byte)"/>
|
|
[CLSCompliant(false)]
|
|
public TagBuilder AddByte([CanBeNull] string name, sbyte value) => AddByte(name, unchecked((byte)value));
|
|
|
|
/// <summary>
|
|
/// Adds a new unnamed <see cref="ByteTag"/> with the specified <paramref name="value"/> to the tree at the current depth.
|
|
/// </summary>
|
|
/// <param name="value">The value of the tag.</param>
|
|
/// <returns>Returns this <see cref="TagBuilder"/> instance for chaining.</returns>
|
|
public TagBuilder AddByte(byte value) => AddByte(null, value);
|
|
|
|
/// <inheritdoc cref="AddByte(sbyte)"/>
|
|
[CLSCompliant(false)]
|
|
public TagBuilder AddByte(sbyte value) => AddByte(null, unchecked((byte)value));
|
|
|
|
/// <summary>
|
|
/// Adds a new <see cref="ShortTag"/> with the specified <paramref name="name"/> and <paramref name="value"/> to the tree at the current depth.
|
|
/// </summary>
|
|
/// <param name="name">The name of the node to add.</param>
|
|
/// <param name="value">The value of the tag.</param>
|
|
/// <returns>Returns this <see cref="TagBuilder"/> instance for chaining.</returns>
|
|
public TagBuilder AddShort([CanBeNull]string name, short value) => AddTag(new ShortTag(name, value));
|
|
|
|
/// <inheritdoc cref="AddShort(string,short)"/>
|
|
[CLSCompliant(false)]
|
|
public TagBuilder AddShort([CanBeNull] string name, ushort value) => AddShort(name, unchecked((short)value));
|
|
|
|
/// <summary>
|
|
/// Adds a new unnamed <see cref="ShortTag"/> with the specified <paramref name="value"/> to the tree at the current depth.
|
|
/// </summary>
|
|
/// <param name="value">The value of the tag.</param>
|
|
/// <returns>Returns this <see cref="TagBuilder"/> instance for chaining.</returns>
|
|
public TagBuilder AddShort(short value) => AddShort(null, value);
|
|
|
|
/// <inheritdoc cref="AddShort(short)"/>
|
|
[CLSCompliant(false)]
|
|
public TagBuilder AddShort(ushort value) => AddShort(null, unchecked((short)value));
|
|
|
|
/// <summary>
|
|
/// Adds a new <see cref="IntTag"/> with the specified <paramref name="name"/> and <paramref name="value"/> to the tree at the current depth.
|
|
/// </summary>
|
|
/// <param name="name">The name of the node to add.</param>
|
|
/// <param name="value">The value of the tag.</param>
|
|
/// <returns>Returns this <see cref="TagBuilder"/> instance for chaining.</returns>
|
|
public TagBuilder AddInt([CanBeNull]string name, int value) => AddTag(new IntTag(name, value));
|
|
|
|
/// <inheritdoc cref="AddInt(string,int)"/>
|
|
[CLSCompliant(false)]
|
|
public TagBuilder AddInt([CanBeNull] string name, uint value) => AddInt(name, unchecked((int)value));
|
|
|
|
/// <summary>
|
|
/// Adds a new unnamed <see cref="IntTag"/> with the specified <paramref name="value"/> to the tree at the current depth.
|
|
/// </summary>
|
|
/// <param name="value">The value of the tag.</param>
|
|
/// <returns>Returns this <see cref="TagBuilder"/> instance for chaining.</returns>
|
|
public TagBuilder AddInt(int value) => AddInt(null, value);
|
|
|
|
/// <inheritdoc cref="AddInt(int)"/>
|
|
[CLSCompliant(false)]
|
|
public TagBuilder AddInt(uint value) => AddInt(null, unchecked((int)value));
|
|
|
|
/// <summary>
|
|
/// Adds a new <see cref="LongTag"/> with the specified <paramref name="name"/> and <paramref name="value"/> to the tree at the current depth.
|
|
/// </summary>
|
|
/// <param name="name">The name of the node to add.</param>
|
|
/// <param name="value">The value of the tag.</param>
|
|
/// <returns>Returns this <see cref="TagBuilder"/> instance for chaining.</returns>
|
|
public TagBuilder AddLong([CanBeNull]string name, long value) => AddTag(new LongTag(name, value));
|
|
|
|
/// <inheritdoc cref="AddLong(string,long)"/>
|
|
[CLSCompliant(false)]
|
|
public TagBuilder AddLong([CanBeNull] string name, ulong value) => AddLong(name, unchecked((long)value));
|
|
|
|
/// <summary>
|
|
/// Adds a new unnamed <see cref="LongTag"/> with the specified <paramref name="value"/> to the tree at the current depth.
|
|
/// </summary>
|
|
/// <param name="value">The value of the tag.</param>
|
|
/// <returns>Returns this <see cref="TagBuilder"/> instance for chaining.</returns>
|
|
public TagBuilder AddLong(long value) => AddLong(null, value);
|
|
|
|
/// <inheritdoc cref="AddLong(long)"/>
|
|
[CLSCompliant(false)]
|
|
public TagBuilder AddLong(ulong value) => AddLong(null, unchecked((long)value));
|
|
|
|
/// <summary>
|
|
/// Adds a new <see cref="FloatTag"/> with the specified <paramref name="name"/> and <paramref name="value"/> to the tree at the current depth.
|
|
/// </summary>
|
|
/// <param name="name">The name of the node to add.</param>
|
|
/// <param name="value">The value of the tag.</param>
|
|
/// <returns>Returns this <see cref="TagBuilder"/> instance for chaining.</returns>
|
|
public TagBuilder AddFloat([CanBeNull]string name, float value) => AddTag(new FloatTag(name, value));
|
|
|
|
/// <summary>
|
|
/// Adds a new unnamed <see cref="FloatTag"/> with the specified <paramref name="value"/> to the tree at the current depth.
|
|
/// </summary>
|
|
/// <param name="value">The value of the tag.</param>
|
|
/// <returns>Returns this <see cref="TagBuilder"/> instance for chaining.</returns>
|
|
public TagBuilder AddFloat(float value) => AddFloat(null, value);
|
|
|
|
/// <summary>
|
|
/// Adds a new <see cref="DoubleTag"/> with the specified <paramref name="name"/> and <paramref name="value"/> to the tree at the current depth.
|
|
/// </summary>
|
|
/// <param name="name">The name of the node to add.</param>
|
|
/// <param name="value">The value of the tag.</param>
|
|
/// <returns>Returns this <see cref="TagBuilder"/> instance for chaining.</returns>
|
|
public TagBuilder AddDouble([CanBeNull]string name, double value) => AddTag(new DoubleTag(name, value));
|
|
|
|
/// <summary>
|
|
/// Adds a new unnamed <see cref="DoubleTag"/> with the specified <paramref name="value"/> to the tree at the current depth.
|
|
/// </summary>
|
|
/// <param name="value">The value of the tag.</param>
|
|
/// <returns>Returns this <see cref="TagBuilder"/> instance for chaining.</returns>
|
|
public TagBuilder AddDouble(double value) => AddDouble(null, value);
|
|
|
|
/// <summary>
|
|
/// Adds a new <see cref="StringTag"/> with the specified <paramref name="name"/> and <paramref name="value"/> to the tree at the current depth.
|
|
/// </summary>
|
|
/// <param name="name">The name of the node to add.</param>
|
|
/// <param name="value">The value of the tag.</param>
|
|
public TagBuilder AddString([CanBeNull]string name, [CanBeNull] string value) => AddTag(new StringTag(name, value));
|
|
|
|
/// <summary>
|
|
/// Adds a new unnamed <see cref="StringTag"/> with the specified <paramref name="value"/> to the tree at the current depth.
|
|
/// </summary>
|
|
/// <param name="value">The value of the tag.</param>
|
|
/// <returns>Returns this <see cref="TagBuilder"/> instance for chaining.</returns>
|
|
public TagBuilder AddString([CanBeNull] string value) => AddString(null, value);
|
|
|
|
/// <summary>
|
|
/// Adds a new <see cref="ByteArrayTag"/> with the specified <paramref name="values"/> to the tree at the current depth.
|
|
/// </summary>
|
|
/// <param name="name">The name of the node to add.</param>
|
|
/// <param name="values">The value(s) that will be included in the tag.</param>
|
|
/// <returns>Returns this <see cref="TagBuilder"/> instance for chaining.</returns>
|
|
public TagBuilder AddByteArray([CanBeNull] string name, params byte[] values) => AddTag(new ByteArrayTag(name, new ReadOnlySpan<byte>(values)));
|
|
|
|
/// <inheritdoc cref="AddByteArray(string,byte[])"/>
|
|
public TagBuilder AddByteArray([CanBeNull] string name, [NotNull] IEnumerable<byte> values) => AddByteArray(name, values.ToArray());
|
|
|
|
/// <summary>
|
|
/// Adds a new unnamed <see cref="StringTag"/> with the specified <paramref name="values"/> to the tree at the current depth.
|
|
/// </summary>
|
|
/// <param name="values">The value(s) that will be included in the tag.</param>
|
|
/// <returns>Returns this <see cref="TagBuilder"/> instance for chaining.</returns>
|
|
public TagBuilder AddByteArray(params byte[] values) => AddByteArray(null, values);
|
|
|
|
/// <inheritdoc cref="AddByteArray(byte[])"/>
|
|
public TagBuilder AddByteArray([NotNull] IEnumerable<byte> values) => AddByteArray(null, values.ToArray());
|
|
|
|
/// <summary>
|
|
/// Adds a new <see cref="ByteArrayTag"/> with the specified <paramref name="values"/> to the tree at the current depth.
|
|
/// </summary>
|
|
/// <param name="name">The name of the node to add.</param>
|
|
/// <param name="values">The value(s) that will be included in the tag.</param>
|
|
/// <returns>Returns this <see cref="TagBuilder"/> instance for chaining.</returns>
|
|
[CLSCompliant(false)]
|
|
public TagBuilder AddByteArray([CanBeNull] string name, params sbyte[] values)
|
|
{
|
|
var span = new ReadOnlySpan<sbyte>(values);
|
|
return AddTag(new ByteArrayTag(name, MemoryMarshal.Cast<sbyte, byte>(span)));
|
|
}
|
|
|
|
/// <inheritdoc cref="AddByteArray(string,byte[])"/>
|
|
[CLSCompliant(false)]
|
|
public TagBuilder AddByteArray([CanBeNull] string name, [NotNull] IEnumerable<sbyte> values) => AddByteArray(name, values.ToArray());
|
|
|
|
/// <summary>
|
|
/// Adds a new unnamed <see cref="StringTag"/> with the specified <paramref name="values"/> to the tree at the current depth.
|
|
/// </summary>
|
|
/// <param name="values">The value(s) that will be included in the tag.</param>
|
|
/// <returns>Returns this <see cref="TagBuilder"/> instance for chaining.</returns>
|
|
[CLSCompliant(false)]
|
|
public TagBuilder AddByteArray(params sbyte[] values) => AddByteArray(null, values);
|
|
|
|
/// <inheritdoc cref="AddByteArray(byte[])"/>
|
|
[CLSCompliant(false)]
|
|
public TagBuilder AddByteArray([NotNull] IEnumerable<sbyte> values) => AddByteArray(null, values.ToArray());
|
|
|
|
|
|
/// <summary>
|
|
/// Adds a new <see cref="IntArrayTag"/> with the specified <paramref name="values"/> to the tree at the current depth.
|
|
/// </summary>
|
|
/// <param name="name">The name of the node to add.</param>
|
|
/// <param name="values">The value(s) that will be included in the tag.</param>
|
|
/// <returns>Returns this <see cref="TagBuilder"/> instance for chaining.</returns>
|
|
public TagBuilder AddIntArray([CanBeNull] string name, params int[] values) => AddTag(new IntArrayTag(name, values as IEnumerable<int>));
|
|
|
|
/// <inheritdoc cref="AddIntArray(string,int[])"/>
|
|
public TagBuilder AddIntArray([CanBeNull] string name, [NotNull] IEnumerable<int> values) => AddIntArray(name, values.ToArray());
|
|
|
|
/// <summary>
|
|
/// Adds a new unnamed <see cref="IntArrayTag"/> with the specified <paramref name="values"/> to the tree at the current depth.
|
|
/// </summary>
|
|
/// <param name="values">The value(s) that will be included in the tag.</param>
|
|
/// <returns>Returns this <see cref="TagBuilder"/> instance for chaining.</returns>
|
|
public TagBuilder AddIntArray(params int[] values) => AddIntArray(null, values);
|
|
|
|
/// <inheritdoc cref="AddIntArray(int[])"/>
|
|
public TagBuilder AddIntArray([NotNull] IEnumerable<int> values) => AddIntArray(null, values.ToArray());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Adds a new <see cref="LongArrayTag"/> with the specified <paramref name="values"/> to the tree at the current depth.
|
|
/// </summary>
|
|
/// <param name="name">The name of the node to add.</param>
|
|
/// <param name="values">The value(s) that will be included in the tag.</param>
|
|
/// <returns>Returns this <see cref="TagBuilder"/> instance for chaining.</returns>
|
|
public TagBuilder AddLongArray([CanBeNull] string name, params long[] values) => AddTag(new LongArrayTag(name, new ReadOnlySpan<long>(values)));
|
|
|
|
/// <inheritdoc cref="AddLongArray(string,long[])"/>
|
|
public TagBuilder AddLongArray([CanBeNull] string name, IEnumerable<long> values) => AddLongArray(name, values.ToArray());
|
|
|
|
/// <summary>
|
|
/// Adds a new unnamed <see cref="LongArrayTag"/> with the specified <paramref name="values"/> to the tree at the current depth.
|
|
/// </summary>
|
|
/// <param name="values">The value(s) that will be included in the tag.</param>
|
|
/// <returns>Returns this <see cref="TagBuilder"/> instance for chaining.</returns>
|
|
public TagBuilder AddLongArray(params long[] values) => AddLongArray(null, values);
|
|
|
|
/// <inheritdoc cref="AddLongArray(long[])"/>
|
|
public TagBuilder AddLongArray([NotNull] IEnumerable<long> values) => AddLongArray(null, values.ToArray());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Adds an existing <see cref="Tag"/> object to the tree at the current depth.
|
|
/// </summary>
|
|
/// <param name="tag">The <see cref="Tag"/> instance to add.</param>
|
|
/// <returns>Returns this <see cref="TagBuilder"/> instance for chaining.</returns>
|
|
/// <exception cref="ArgumentException">Thrown if adding to a <see cref="ListTag"/> node, and the type does not match.</exception>
|
|
/// <exception cref="ArgumentNullException">Thrown when <paramref name="tag"/> is <see langword="null"/>.</exception>
|
|
public TagBuilder AddTag([NotNull] Tag tag)
|
|
{
|
|
var top = tree.Peek();
|
|
top.Add(tag ?? throw new ArgumentNullException(nameof(tag)));
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Opens a new <see cref="ListTag"/> section, increasing the current depth level by one.
|
|
/// </summary>
|
|
/// <param name="childType">The <see cref="TagType"/> of the child items this list will contain.</param>
|
|
/// <param name="name">The name to apply to the <see cref="ListTag"/>, or <see langword="null"/> to omit a name.</param>
|
|
/// <returns>Returns this <see cref="TagBuilder"/> instance for chaining.</returns>
|
|
/// <seealso cref="EndList"/>
|
|
public TagBuilder BeginList(TagType childType, [CanBeNull] string name = null)
|
|
{
|
|
var list = new ListTag(name, childType);
|
|
AddTag(list);
|
|
tree.Push(list);
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Closes the current <see cref="ListTag"/> section and decreases the <see cref="Depth"/> by one. Does nothing if the the current node does not
|
|
/// represent a <see cref="ListTag"/>.
|
|
/// </summary>
|
|
/// <returns>Returns this <see cref="TagBuilder"/> instance for chaining.</returns>
|
|
/// <seealso cref="BeginList"/>
|
|
public TagBuilder EndList()
|
|
{
|
|
if (tree.TryPeek(out var result) && result is ListTag)
|
|
tree.Pop();
|
|
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Opens a new <see cref="CompoundTag"/> section, increasing the current depth level by one.
|
|
/// </summary>
|
|
/// <param name="name">The name to apply to the <see cref="ListTag"/>, or <see langword="null"/> to omit a name.</param>
|
|
/// <returns>Returns this <see cref="TagBuilder"/> instance for chaining.</returns>
|
|
/// <seealso cref="EndCompound"/>
|
|
public TagBuilder BeginCompound([CanBeNull] string name = null)
|
|
{
|
|
var compound = new CompoundTag(name);
|
|
AddTag(compound);
|
|
tree.Push(compound);
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Closes the current <see cref="CompoundTag"/> section and decreases the <see cref="Depth"/> by one. Does nothing if the the current node does not
|
|
/// represent a <see cref="CompoundTag"/>.
|
|
/// </summary>
|
|
/// <returns>Returns this <see cref="TagBuilder"/> instance for chaining.</returns>
|
|
/// <seealso cref="BeginCompound"/>
|
|
public TagBuilder EndCompound()
|
|
{
|
|
if (tree.Count > 1 && tree.TryPeek(out var result) && result is CompoundTag)
|
|
tree.Pop();
|
|
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Closes the current <see cref="CompoundTag"/> or <see cref="ListTag"/> section and decreases the <see cref="Depth"/> by one.
|
|
/// </summary>
|
|
/// <returns>Returns this <see cref="TagBuilder"/> instance for chaining.</returns>
|
|
/// <remarks>This method does nothing if the current location is already at the top-level.</remarks>
|
|
public TagBuilder End()
|
|
{
|
|
if ((tree.Peek() is ListTag) || (tree.Count > 1 && tree.Peek() is CompoundTag))
|
|
tree.Pop();
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Closes any open compound/list sections, and returns the result as a <see cref="CompoundTag"/>.
|
|
/// </summary>
|
|
/// <remarks>Invoking this method moves the current <see cref="Depth"/> back to the top-level.</remarks>
|
|
/// <returns>A <see cref="CompoundTag"/> representing the result of this <see cref="TagBuilder"/> tree.</returns>
|
|
public CompoundTag Create()
|
|
{
|
|
tree.Clear();
|
|
tree.Push(root);
|
|
return root;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new <see cref="CompoundTag"/> and pushes it to the current scope level, returning a <see cref="Context"/> object that pulls the current
|
|
/// scope back out one level when disposed.
|
|
/// </summary>
|
|
/// <param name="name">The name to apply to the <see cref="ListTag"/>, or <see langword="null"/> to omit a name.</param>
|
|
/// <returns>A <see cref="Context"/> that will close the <see cref="CompoundTag"/> when disposed.</returns>
|
|
/// <remarks>This is essentially no different than <see cref="BeginCompound"/> and <see cref="EndCompound"/> but can use `using` blocks to distinguish scope.</remarks>
|
|
public Context NewCompound([CanBeNull] string name)
|
|
{
|
|
BeginCompound(name);
|
|
return new Context(tree.Peek(), EndCompound);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new <see cref="ListTag"/> and pushes it to the current scope level, returning a <see cref="Context"/> object that pulls the current
|
|
/// scope back out one level when disposed.
|
|
/// </summary>
|
|
/// <param name="childType">The <see cref="TagType"/> of the child items this list will contain.</param>
|
|
/// <param name="name">The name to apply to the <see cref="ListTag"/>, or <see langword="null"/> to omit a name.</param>
|
|
/// <returns>A <see cref="Context"/> that will close the <see cref="ListTag"/> when disposed.</returns>
|
|
/// <remarks>This is essentially no different than <see cref="BeginList"/> and <see cref="EndList"/> but can use `using` blocks to distinguish scope.</remarks>
|
|
public Context NewList(TagType childType, [CanBeNull] string name)
|
|
{
|
|
BeginList(childType, name);
|
|
return new Context(tree.Peek(), EndList);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Represents the context of a single "level" of depth into a <see cref="TagBuilder"/> AST.
|
|
/// </summary>
|
|
/// <remarks>Implements <see cref="IDisposable"/> to that each node can used with <c>using</c> statements for easily distinguishable scope.</remarks>
|
|
[PublicAPI]
|
|
public class Context: IDisposable
|
|
{
|
|
internal delegate TagBuilder CloseHandler();
|
|
private readonly CloseHandler closeHandler;
|
|
|
|
/// <summary>
|
|
/// Gets the top-level tag for this context.
|
|
/// </summary>
|
|
[NotNull]
|
|
public TagContainer Tag { get; }
|
|
|
|
internal Context([NotNull] TagContainer tag, [NotNull] CloseHandler handler)
|
|
{
|
|
Tag = tag;
|
|
closeHandler = handler;
|
|
}
|
|
|
|
/// <summary>Closes this context.</summary>
|
|
public void Dispose()
|
|
{
|
|
Console.WriteLine(Tag);
|
|
closeHandler.Invoke();
|
|
}
|
|
}
|
|
}
|
|
} |