Esempio n. 1
0
        /// <summary>Formats a config node key/value line.</summary>
        /// <param name="indentation">The indentation in tabs. Each tab is 8 spaces.</param>
        /// <param name="key">The key string.</param>
        /// <param name="value">
        /// The value string. It can contain multiple lines separated by a "\n" symbols.
        /// </param>
        /// <param name="meta">
        /// The meta block of this field. Only the applicable properties will be used. If set to <c>null</c>, then no
        /// comments/MM logic be dispatched.
        /// </param>
        /// <returns>A properly formatted line.</returns>
        static string MakeConfigNodeLine(int indentation, string key, string value, MetaBlock meta = null)
        {
            meta ??= new MetaBlock();
            var fieldName  = (meta.mmCommand ?? "") + key + (meta.mmArguments ?? "");
            var operand    = (meta.mmOperator ?? "") + "=";
            var fieldValue = EscapeValue(value);
            var comment    = (meta.inlineComment != null ? " // " + meta.inlineComment : "");

            return(new string('\t', indentation) + fieldName + " " + operand + " " + fieldValue + comment);
        }
        /// <summary>Extracts the strings that look like the localized tags.</summary>
        /// <remarks>
        /// This methods looks for the values that are localized in way it's done in the stock parts.
        /// </remarks>
        /// <param name="part">The part to extract the items for.</param>
        /// <param name="config">
        /// The config node of the part. It's an extend version with the comments.
        /// </param>
        /// <returns>The list of extracted items.</returns>
        static List <LocItem> EmitItemsForNode(AvailablePart part, ConfigNode config)
        {
            var res = new List <LocItem>();

            // Go through all the fields to detect if there are localized optional fields.
            foreach (var field in config.values.Cast <ConfigNode.Value>())
            {
                if (string.IsNullOrEmpty(field.comment))
                {
                    continue;
                }
                var meta    = MetaBlock.MakeFromString(field.comment);
                var comment = meta.inlineComment ?? "";
                var match   = Regex.Match(comment, @"^(#[a-zA-Z0-9_-]+)\s*=\s*(.+?)$");
                if (!match.Success)
                {
                    continue; // Not localized.
                }
                var locTag   = match.Groups[1].Value;
                var locValue = field.value;
                if (field.value == locTag)
                {
                    // In case of the tag is not get resolved, use the default template.
                    DebugEx.Warning(
                        "Field '{0}' in part {1} looks localized, but the tag '{2}' is not found"
                        + " (suggested default value: '{3}')",
                        field.name, part.name, locTag, match.Groups[2].Value);
                    locValue = match.Groups[2].Value;
                }
                var item = new LocItem()
                {
                    groupKey        = "Part: " + part.name,
                    fullFilePath    = part.configFileFullName,
                    locTag          = locTag,
                    locDefaultValue = locValue,
                };
                res.Add(item);
            }

            // Scan the nested nodes.
            foreach (var nestedNode in config.GetNodes())
            {
                res.AddRange(EmitItemsForNode(part, nestedNode));
            }

            return(res);
        }
Esempio n. 3
0
 /// <summary>Merges localizable values from one config node to another.</summary>
 /// <remarks>
 /// The values in the nodes must be in the same order. The <paramref name="toNode"/> is allowed
 /// to have more values, the extra values will be silently skipped.
 /// </remarks>
 /// <param name="toNode">The node to merge value to. It's a regular node from the part prefab.</param>
 /// <param name="fromNode">
 /// The node to merge values from. It must have comments loaded. Note, that the comments are encoded via the
 /// <see cref="MetaBlock"/>.
 /// </param>
 static void MergeLocalizableValues(ConfigNode toNode, ConfigNode fromNode)
 {
     for (var i = 0; i < fromNode.values.Count && i < toNode.values.Count; i++)
     {
         var fromValue = fromNode.values[i];
         var toValue   = toNode.values[i];
         if (fromValue.name != toValue.name)
         {
             DebugEx.Error("Cannot merge config nodes.\nTO:\n{0}\nFROM:\n{1}", toNode, fromNode);
             return;
         }
         var metaBlock = MetaBlock.MakeFromString(fromValue.comment);
         if (IsLocalizationTag(metaBlock.inlineComment, firstWordOnly: true))
         {
             toValue.value   = fromValue.value;
             toValue.comment = metaBlock.inlineComment;
         }
     }
     for (var i = 0; i < fromNode.nodes.Count && i < toNode.nodes.Count; i++)
     {
         MergeLocalizableValues(toNode.nodes[i], fromNode.nodes[i]);
     }
 }
        /// <summary>Extracts the localization items from the part's config.</summary>
        /// <param name="part">The part to extract items for.</param>
        /// <returns>All the localization items for the part.</returns>
        public static List <LocItem> EmitItemsForPart(AvailablePart part)
        {
            var res = new List <LocItem>();
            // The part's config in the AvailablePart doesn't have all the fields and lacks the comments.
            // We do need the comments to resolve the field tag names, so load via a custom method.
            // In case of the custom loading method fails, use the stock one without the comments support.
            var config = (ConfigStore.LoadConfigWithComments(part.configFileFullName)
                          ?? ConfigNode.Load(part.configFileFullName)).GetNode("PART");

            if (config == null)
            {
                DebugEx.Error("Failed to load part's config: partName={0}, configUrl={1}",
                              part.name, part.configFileFullName);
                return(res);
            }

            // Go through the fields we know must be localized.
            foreach (var fieldName in LocalizablePartFields)
            {
                var field = config.values.Cast <ConfigNode.Value>().FirstOrDefault(x => x.name == fieldName);
                if (field == null)
                {
                    DebugEx.Warning("Field '{0}' is not found in the part {1} config", fieldName, part.name);
                    continue;
                }
                config.values.Remove(field); // Don't handle it down the stream.
                string locTag          = null;
                string locDefaultValue = null;
                var    meta            = MetaBlock.MakeFromString(field.comment);
                if (LocalizationManager.IsLocalizationTag(meta.inlineComment, firstWordOnly: true))
                {
                    var match = Regex.Match(meta.inlineComment, @"^(#[a-zA-Z0-9_-]+)\s*=\s*(.+?)$");
                    if (match.Success)
                    {
                        locTag = match.Groups[1].Value;
                        if (field.value == locTag)
                        {
                            // Part's tag localization failed, use the default text.
                            locDefaultValue = match.Groups[2].Value;
                        }
                    }
                    else
                    {
                        DebugEx.Warning("Cannot resolve default localization tag in field {0} for part {1}: {2}",
                                        fieldName, config.GetValue("name"), meta.inlineComment);
                    }
                }
                // ReSharper disable once UseStringInterpolation
                var fieldOrderStr = string.Format(
                    "\0x00_{0:000}_{1}", // Use zero prefix to place it before anything else.
                    Controller.partFieldsSorting.IndexOf(fieldName, StringComparison.Ordinal),
                    fieldName);
                var item = new LocItem()
                {
                    groupKey        = "Part: " + part.name,
                    sortKey         = fieldOrderStr,
                    fullFilePath    = part.configFileFullName,
                    locTag          = locTag ?? MakePartFieldLocalizationTag(config.GetValue("name"), fieldName),
                    locDefaultValue = locDefaultValue ?? field.value,
                };
                res.Add(item);
            }

            res.AddRange(EmitItemsForNode(part, config));

            return(res);
        }
Esempio n. 5
0
        /// <summary>Recursively collects and serializes the fields in the nodes.</summary>
        /// <remarks>
        /// Supports special field name <c>__commentField</c> to output the line comments. If the line
        /// comment is empty, then only an empty line is output into the result.
        /// </remarks>
        /// <param name="res"></param>
        /// <param name="node"></param>
        /// <param name="indentation"></param>
        static void SerializeNode(StringBuilder res, ConfigNode node, int indentation)
        {
            var indentSpaces = new string('\t', indentation);
            var nodeMeta     = MetaBlock.MakeFromString(node.comment);

            foreach (var trailingLine in nodeMeta.trailingLines)
            {
                if (trailingLine.isEmptyLine)
                {
                    res.AppendLine();
                }
                else
                {
                    res.AppendLine(indentSpaces + "// " + trailingLine.value);
                }
            }
            var nodeText          = (nodeMeta.mmCommand ?? "") + node.name + (nodeMeta.mmArguments ?? "");
            var openBlockComment  = nodeMeta.openBlockComment != null ? " // " + nodeMeta.openBlockComment : "";
            var closeBlockComment = nodeMeta.closeBlockComment != null ? " // " + nodeMeta.closeBlockComment : "";
            var inlineComment     = nodeMeta.inlineComment != null ? " // " + nodeMeta.inlineComment : "";

            // Check for an empty block. Write it in a short form.
            if (node.values.Count == 0 && node.nodes.Count == 0 && openBlockComment == "" && inlineComment == "")
            {
                res.AppendLine(indentSpaces + nodeText + " {}" + closeBlockComment);
                return;
            }

            res.AppendLine(indentSpaces + nodeText + inlineComment);
            res.AppendLine(indentSpaces + "{" + openBlockComment);
            indentation++;
            var indentBlock = indentSpaces + '\t';

            var fields = node.values.Cast <ConfigNode.Value>();

            foreach (var field in fields)
            {
                var meta = MetaBlock.MakeFromString(field.comment);
                foreach (var line in meta.trailingLines)
                {
                    if (line.isEmptyLine)
                    {
                        res.AppendLine("");
                    }
                    else
                    {
                        res.AppendLine(indentBlock + "// " + line.value);
                    }
                }
                if (meta.isFakeField)
                {
                    continue; // No actual field is needed.
                }
                res.AppendLine(MakeConfigNodeLine(indentation, field.name, field.value, meta: meta));
            }
            if (node.CountNodes > 0)
            {
                foreach (var childNode in node.GetNodes())
                {
                    SerializeNode(res, childNode, indentation);
                }
            }
            res.AppendLine(indentSpaces + "}" + closeBlockComment);
        }