private void ParseString()
        {
            String = GetCachedString(Value, Localized);
            if (String == null)
            {
                try
                {
                    if (!Localized)
                    {
                        String = CustomStringGroup.ParseCustomString(Value, FormatParameters.Length);
                    }
                    else
                    {
                        String = LocalizedString.ParseLocalizedString(Value, 0, Value, FormatParameters.Length);
                    }

                    lock (_cacheLock) _cache.Add(String);
                }
                catch (StringParseFailedException ex)
                {
                    if (ex.StringIndex == -1)
                    {
                        _parseInfo.Script.Diagnostics.Error(ex.Message, _stringRange);
                    }
                    else
                    {
                        int errorStart = _stringRange.Start.Character + 1 + ex.StringIndex;
                        _parseInfo.Script.Diagnostics.Error(ex.Message, new DocRange(
                                                                new DocPos(_stringRange.Start.Line, errorStart),
                                                                new DocPos(_stringRange.Start.Line, errorStart + ex.Length)
                                                                ));
                    }
                }
            }
            _parseInfo.TranslateInfo.GetComponent <StringSaverComponent>().Strings.Add(String);
        }
        public static CustomStringGroup ParseCustomString(string value, int parameterCount)
        {
            // Look for <#>s
            var formats = Regex.Matches(value, "<([0-9]+)>").ToArray();

            CustomStringGroup customStringGroup = new CustomStringGroup(value);

            // If there are no formats, return the custom string normally.
            if (formats.Length == 0)
            {
                customStringGroup.Segments = new CustomStringSegment[] {
                    new CustomStringSegment(value)
                };
                return(customStringGroup);
            }

            // The Overwatch workshop only supports 3 formats in a string.
            // The following code will split the string into multiple sections so it can support more.
            // Split the string after every 3 unique formats, for example:
            //                        v split here
            // <0> this <1> <0> is a <3> custom <4> string <5>

            List <FormatParameter> stringGroupParameters = new List <FormatParameter>(); // The current group of formats.
            List <StringGroup>     stringGroups          = new List <StringGroup>();     // Stores information about each section in the string.
            List <int>             unique = new List <int>();                            // Stores the list of each unique format id. The count shouldn't go above 3.

            for (int i = 0; i < formats.Length; i++)
            {
                FormatParameter parameter = new FormatParameter(formats[i]);

                // If the format id is more than the number of parameters, throw a syntax error.
                if (parameter.Parameter >= parameterCount)
                {
                    throw new StringParseFailedException(
                              $"Can't set the <{parameter.Parameter}> format, there are only {parameterCount} parameters.",
                              parameter.Match.Index,
                              parameter.Match.Length
                              );
                }

                // If there is already 3 unique IDs, create a new section.
                if (unique.Count == 3 && !unique.Contains(parameter.Parameter))
                {
                    stringGroups.Add(new StringGroup(stringGroupParameters.ToArray()));
                    stringGroupParameters.Clear();
                    unique.Clear();
                }

                stringGroupParameters.Add(parameter);

                // If the current format ID is new, add it to the unique list.
                if (!unique.Contains(parameter.Parameter))
                {
                    unique.Add(parameter.Parameter);
                }
            }

            // Add tailing formats to a new section.
            stringGroups.Add(new StringGroup(stringGroupParameters.ToArray()));

            // Convert each section to a custom string.
            customStringGroup.Segments = new CustomStringSegment[stringGroups.Count];
            for (int i = 0; i < stringGroups.Count; i++)
            {
                // start is either the start of the string or the end of the last section.
                int start = i == 0 ? 0 : stringGroups[i - 1].EndIndex;
                // end is the index of last format in the section unless this is the last section, then it will be the end of the string.
                int end = i == stringGroups.Count - 1 ? value.Length : stringGroups[i].EndIndex;

                string groupString = value.Substring(start, end - start);

                // Returns an array of all unique formats in the current section.
                var formatGroups = stringGroups[i].Formats
                                   .GroupBy(g => g.Parameter)
                                   .Select(g => g.First())
                                   .ToArray();

                // groupParameters is {0}, {1}, and {2}. Length should be between 1 and 3.
                int[] groupParameters = new int[formatGroups.Length];
                for (int g = 0; g < formatGroups.Length; g++)
                {
                    int parameter = formatGroups[g].Parameter;
                    groupString        = groupString.Replace("<" + parameter + ">", "{" + g + "}");
                    groupParameters[g] = parameter;
                }
                customStringGroup.Segments[i] = new CustomStringSegment(groupString, groupParameters);
            }
            return(customStringGroup);
        }