/// <summary>Reads part's config file from file for the requested part.</summary> /// <remarks> /// It loads the config with the inline comments. For the multi-part configs the right node is /// located. If the config has MM patches for the "PART" section, then they are dropped, but a /// warning is logged. /// </remarks> /// <param name="partInfo">The part to get config for.</param> /// <returns>The config or <c>null</c> if config cannot be found or loaded.</returns> static ConfigNode GetPartPrefabConfig(AvailablePart partInfo) { if (string.IsNullOrEmpty(partInfo.configFileFullName)) { DebugEx.Error("Skip part {0} since it doesn't have a config", partInfo.name); return(null); } var config = ConfigStore.LoadConfigWithComments(partInfo.configFileFullName); if (config == null || config.nodes.Count == 0) { DebugEx.Error("Config node is invalid for the part {0}\n{1}", partInfo.name, config); return(null); } ConfigNode result = null; for (var i = 0; i < config.nodes.Count; i++) { var node = config.nodes[i]; if (node.name != "PART") { DebugEx.Warning("Non-part node in config of part {0}: {1}", partInfo.name, node.name); continue; } // KSP mangles with the part names translating "." to "_" and back. So just in case do it on // the both sides. if (node.GetValue("name").Replace(".", "_") != partInfo.name.Replace(".", "_")) { DebugEx.Warning("Node in config of part '{0}' doesn't match the part name: '{1}'", partInfo.name, node.GetValue("name")); continue; } if (result == null) { result = node; } else { DebugEx.Warning( "Skipping node #{0} in config of part {1} due to duplication", i, partInfo.name); } } return(result); }
/// <summary> /// Patches the part configs so that they refer the tags for the localizable fileds, and saves the /// modified fiels in the export location. /// </summary> /// <remarks></remarks> /// <param name="parts">The parts to patch.</param> void GuiExportPartConfigs(IEnumerable <PartsRecord> parts) { var exportParts = parts.SelectMany(x => x.parts); var exportPath = KspPaths.GetModsDataFilePath(this, "Parts/"); foreach (var part in exportParts) { var config = ConfigStore.LoadConfigWithComments( part.configFileFullName, localizeValues: false); if (config == null) { Debug.LogErrorFormat( "Cannot load config file for part {0}: {1}", part.name, part.configFileFullName); continue; } var partNode = config.GetNode("PART"); foreach (var fieldName in Extractor.localizablePartFields) { var field = partNode.values.Cast <ConfigNode.Value>() .FirstOrDefault(x => x.name == fieldName); if (field == null) { Debug.LogWarningFormat("Field '{0}' is not found in the part {1} config", fieldName, part.name); continue; } if (field.value.StartsWith("#", StringComparison.Ordinal)) { continue; // It's already localized. } var locTag = Extractor.MakePartFieldLocalizationTag(part.name, fieldName); field.comment = locTag + " = " + field.value; field.value = locTag; } var tgtPath = exportPath + part.name.Replace(".", "_") + ".cfg"; Debug.LogWarningFormat("Saving patched part config into: {0}", tgtPath); ConfigStore.SaveConfigWithComments(config, tgtPath); } ShowCompletionDialog( ConfigSavedDlgTitle, ConfigsSavedInFolderTxt.Format(exportParts.Count(), exportPath)); }
/// <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); }
/// <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) { Debug.LogErrorFormat("Failed to load part's config: partName={0}, configUrl={1}", part.name, part.configFileFullName); return(res); } // Go thru 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) { Debug.LogWarningFormat("Field '{0}' is not found in the part {1} config", fieldName, part.name); continue; } string locTag = null; string locDefaultValue = null; if (!string.IsNullOrEmpty(field.comment) && field.comment.StartsWith("#", StringComparison.Ordinal)) { var match = Regex.Match(field.comment, @"^(#[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 { Debug.LogWarningFormat( "Cannot resolve defult localization tag in field {0} for part {1}: {2}", fieldName, config.GetValue("name"), field.comment); } } var item = new LocItem() { groupKey = "Part: " + part.name, fullFilePath = part.configFileFullName, locTag = locTag ?? MakePartFieldLocalizationTag(config.GetValue("name"), fieldName), locDefaultValue = locDefaultValue ?? field.value, }; res.Add(item); } res.AddRange(EmitItemsForNode(part, config)); return(res); }