using System; using System.Collections.Generic; using System.Diagnostics; using System.Text; using UnityEngine.Localization.SmartFormat; using UnityEngine.Pool; namespace UnityEngine.Localization.Pseudo { /// /// A message fragment represents part of a . /// A Message can be broken into multiple and fragments. /// public abstract class MessageFragment { /// /// The original string being parsed. /// protected string m_OriginalString; /// /// The start index for this fragment from . /// protected int m_StartIndex; /// /// The end index for this fragment from . /// protected int m_EndIndex; string m_CachedToString; /// /// Total length of the fragment. /// public int Length => m_StartIndex == -1 ? m_OriginalString.Length : m_EndIndex - m_StartIndex; /// /// The message the fragment is part of. /// public Message @Message { get; private set; } internal void Initialize(Message parent, string original, int start, int end) { @Message = parent; m_OriginalString = original; m_StartIndex = start; m_EndIndex = end; m_CachedToString = null; } internal void Initialize(Message parent, string text) { @Message = parent; m_OriginalString = text; m_StartIndex = -1; m_EndIndex = -1; m_CachedToString = null; } /// public WritableMessageFragment CreateTextFragment(int start, int end) { var frag = WritableMessageFragment.Pool.Get(); var startIndex = m_StartIndex == -1 ? start : m_StartIndex + start; var endIndex = m_StartIndex == -1 ? end : m_StartIndex + end; frag.Initialize(@Message, m_OriginalString, startIndex, endIndex); return frag; } /// public ReadOnlyMessageFragment CreateReadonlyTextFragment(int start, int end) { var frag = ReadOnlyMessageFragment.Pool.Get(); var startIndex = m_StartIndex == -1 ? start : m_StartIndex + start; var endIndex = m_StartIndex == -1 ? end : m_StartIndex + end; frag.Initialize(@Message, m_OriginalString, startIndex, endIndex); return frag; } public override string ToString() { if (m_CachedToString == null) m_CachedToString = m_StartIndex == -1 ? m_OriginalString : m_OriginalString.Substring(m_StartIndex, m_EndIndex - m_StartIndex); return m_CachedToString; } internal void BuildString(StringBuilder builder) { if (m_StartIndex == -1) builder.Append(m_OriginalString); else builder.Append(m_OriginalString, m_StartIndex, m_EndIndex - m_StartIndex); } /// /// Returns the char at the specified index. /// /// The index of the char to return from . /// public char this[int index] { get { var startIdx = m_StartIndex == -1 ? 0 : m_StartIndex; return m_OriginalString[startIdx + index]; } } } /// /// Represents a message fragment that can be modified. /// [DebuggerDisplay("Writable: {Text}")] public class WritableMessageFragment : MessageFragment { internal static readonly ObjectPool Pool = new ObjectPool( () => new WritableMessageFragment(), collectionCheck: false); /// /// The text contained in this fragment. /// public string Text { get => ToString(); set => Initialize(Message, value); } } /// /// Represents a message fragment that should be preserved and mot modified. /// [DebuggerDisplay("ReadOnly: {Text}")] public class ReadOnlyMessageFragment : MessageFragment { internal static readonly ObjectPool Pool = new ObjectPool( () => new ReadOnlyMessageFragment(), collectionCheck: false); /// /// The text contained in this fragment. /// public string Text => ToString(); } /// /// A message is a piece of text that can be broken down into multiple sub fragments. A fragment can be writable or read only. /// A read only fragment indicates that the sub string should be preserved and not modified by ant pseudo methods. /// public class Message { internal static readonly ObjectPool Pool = new ObjectPool( () => new Message(), collectionCheck: false); /// /// The original text before it was broken into fragments. /// public string Original { get; private set; } /// /// A message is comprised of writable and readonly fragments. Readonly fragments are those that should be preserved such as xml/rich text tags. /// public List Fragments { get; private set; } = new List(); /// /// Total length of the Message including all Fragments. /// public int Length { get { int l = 0; foreach (var f in Fragments) { l += f.Length; } return l; } } /// /// Creates a new which represents a sub string of the original. /// Fragments are created using an ObjectPool so they can be reused. Use to return the fragment. /// /// Original string /// Sub string start /// Sub string end /// A new fragment. public WritableMessageFragment CreateTextFragment(string original, int start, int end) { var frag = WritableMessageFragment.Pool.Get(); frag.Initialize(this, original, start, end); return frag; } /// /// Creates a new which represents a string. /// Fragments are created using an ObjectPool so they can be reused. Use to /// return the fragment or allow the Message to handle returning the fragment if it is part of . /// /// The source string. /// A new fragment. public WritableMessageFragment CreateTextFragment(string original) { var frag = WritableMessageFragment.Pool.Get(); frag.Initialize(this, original); return frag; } /// /// Creates a which represents a sub string of the original that should /// be preserved and not modified by any other pseudo methods. /// Fragments are created using an ObjectPool so they can be reused. Use to /// return the fragment or allow the Message to handle returning the fragment if it is part of . /// /// Original string /// Sub string start /// Sub string end /// A new fragment. public ReadOnlyMessageFragment CreateReadonlyTextFragment(string original, int start, int end) { var frag = ReadOnlyMessageFragment.Pool.Get(); frag.Initialize(this, original, start, end); return frag; } /// /// Creates a which represents string that should be preserved and not modified by any other pseudo methods. /// Fragments are created using an ObjectPool so they can be reused. Use to /// return the fragment or allow the Message to handle returning the fragment if it is part of . /// /// The source string. /// A new fragment. public ReadOnlyMessageFragment CreateReadonlyTextFragment(string original) { var frag = ReadOnlyMessageFragment.Pool.Get(); frag.Initialize(this, original); return frag; } /// /// Replaces the Fragments in and returns the previous fragment back to the ObjectPool so it can be reused. /// /// Fragment to replace. /// Replacement Fragment. public void ReplaceFragment(MessageFragment original, MessageFragment replacement) { var index = Fragments.IndexOf(original); if (index == -1) throw new Exception($"Can not replace Fragment {original.ToString()} that is not part of the message."); Fragments[index] = replacement; ReleaseFragment(original); } /// /// Returns a Fragment back to its ObjectPool so it can be used again. /// /// public void ReleaseFragment(MessageFragment fragment) { if (fragment is WritableMessageFragment wmf) WritableMessageFragment.Pool.Release(wmf); else if (fragment is ReadOnlyMessageFragment romf) ReadOnlyMessageFragment.Pool.Release(romf); } /// /// Creates a new message to represent a piece of text. /// /// The source text. /// A new Message instance. internal static Message CreateMessage(string text) { var message = Pool.Get(); message.Fragments.Add(message.CreateTextFragment(text)); message.Original = text; return message; } internal void Release() { foreach (var f in Fragments) { ReleaseFragment(f); } Fragments.Clear(); Pool.Release(this); } public override string ToString() { using (StringBuilderPool.Get(out var stringBuilder)) { foreach (var f in Fragments) { f.BuildString(stringBuilder); } return stringBuilder.ToString(); } } } }