/// <summary> /// Evaluate placeholders into string values. /// </summary> /// <param name="line">original requesting line</param> /// <param name="resolvedLine">(optional Line that was matched from IAsset or inlines</param> /// <param name="pluralLine">(optional) Line that was matched from IAsset or inlines for plural value</param> /// <param name="placeholders"></param> /// <param name="features">contextual data</param> /// <param name="placeholder_values">collection where strings are placed, one for each placeholder</param> /// <param name="culture">the culture in which to evaluate</param> void EvaluatePlaceholderValues(ILine line, ILine resolvedLine, ILine pluralLine, IPlaceholder[] placeholders, ref LineFeatures features, ref StructList12 <string> placeholder_values, CultureInfo culture) { PlaceholderExpressionEvaluator placeholder_evaluator = new PlaceholderExpressionEvaluator(); placeholder_evaluator.Args = features.ValueArgs; placeholder_evaluator.FunctionEvaluationCtx.Culture = culture; placeholder_evaluator.FunctionEvaluationCtx.Line = line; placeholder_evaluator.FunctionEvaluationCtx.ResolvedLine = resolvedLine; placeholder_evaluator.FunctionEvaluationCtx.PluralLine = pluralLine; placeholder_evaluator.FunctionEvaluationCtx.StringResolver = this; placeholder_evaluator.FunctionEvaluationCtx.EnumResolver = EnumResolver; if (features.FormatProviders.Count == 1) { placeholder_evaluator.FunctionEvaluationCtx.FormatProvider = features.FormatProviders[0]; } else if (features.FormatProviders.Count > 1) { placeholder_evaluator.FunctionEvaluationCtx.FormatProvider = new FormatProviderComposition(features.FormatProviders.ToArray()); } if (features.Functions.Count == 1) { placeholder_evaluator.FunctionEvaluationCtx.Functions = features.Functions[0]; } else if (features.Functions.Count > 1) { placeholder_evaluator.FunctionEvaluationCtx.Functions = new FunctionsMap(features.Functions); } for (int i = 0; i < placeholders.Length; i++) { try { // Get placeholder IPlaceholder ph = placeholders[i]; // Evaluate value string ph_value = placeholder_evaluator.toString(placeholder_evaluator.Evaluate(ph.Expression)); // Add to array placeholder_values.Add(ph_value); // Update code features.Status.UpPlaceholder(placeholder_evaluator.Status); } catch (Exception e) { // Log exceptions features.Log(e); // Mark error features.Status.UpPlaceholder(LineStatus.PlaceholderErrorExpressionEvaluation); // Put empty value placeholder_values.Add(null); } } }
/// <summary> /// Resolve keys into a line. Searches from asset, inlines and key itself. /// /// If resolving causes an exception, it is caught, logged and status in <paramref name="features"/> is updated. /// </summary> /// <param name="key"></param> /// <param name="features"></param> /// <param name="culture">The culture that matched</param> /// <returns>matching line or null</returns> ILine ResolveKeyToLine(ILine key, ref LineFeatures features, ref CultureInfo culture) { try { // Tmp variable ILine line = null; // Explicit culture request if (culture != null) { // 0 - main preference, 1+ - fallback preferences int preferenceIndex = 0; for (CultureInfo ci = culture, prev = null; ci != null && ci != prev; prev = ci, ci = ci.Parent) { ILine key_with_culture = preferenceIndex == 0 ? key : key.Prune(NoCultureQualifier.Default).Culture(ci); // Should invariant culture be tested here?? foreach (var stage in ResolveSequence) { switch (stage) { // Try asset case ResolveSource.Asset: for (int i = 0; i < features.Assets.Count; i++) { try { IAsset asset = features.Assets[i]; if ((line = asset.GetLine(key_with_culture)) != null) { // Up status with source info features.Status.UpResolve(LineStatus.ResolveOkFromAsset); // Up status with culture info LineStatus cultureStatus = preferenceIndex == 0 // Request matched with first preference ? (ci.Name == "" ? LineStatus.CultureOkRequestMatchedInvariantCulture : ci.IsNeutralCulture ? LineStatus.CultureOkRequestMatchedLanguage : LineStatus.CultureOkRequestMatchedLanguageAndRegion) // Request matched with a fallback preference : (ci.Name == "" ? Localization.LineStatus.CultureErrorRequestMatchedInvariantCulture : ci.IsNeutralCulture ? LineStatus.CultureWarningRequestMatchedLanguage : Localization.LineStatus.CultureWarningRequestMatchedLanguageAndRegion); features.Status.UpCulture(cultureStatus); features.ScanFeatures(line); return(line); } } catch (Exception e) { features.Status.UpResolve(LineStatus.ResolveErrorAssetException); features.Log(e); } } break; // Try inlines case ResolveSource.Inline: for (int i = 0; i < features.Inlines.Count; i++) { try { if (features.Inlines[i].TryGetValue(key_with_culture, out line)) { // Up status with source info features.Status.UpResolve(LineStatus.ResolveOkFromInline); // Up status with culture info LineStatus cultureStatus = preferenceIndex == 0 // Request matched with first preference ? (ci.Name == "" ? LineStatus.CultureOkRequestMatchedInvariantCulture : ci.IsNeutralCulture ? LineStatus.CultureOkRequestMatchedLanguage : LineStatus.CultureOkRequestMatchedLanguageAndRegion) // Request matched with a fallback preference : (ci.Name == "" ? Localization.LineStatus.CultureErrorRequestMatchedInvariantCulture : ci.IsNeutralCulture ? LineStatus.CultureWarningRequestMatchedLanguage : Localization.LineStatus.CultureWarningRequestMatchedLanguageAndRegion); features.Status.UpCulture(cultureStatus); features.ScanFeatures(line); return(line); } } catch (Exception e) { features.Status.UpResolve(LineStatus.ResolveErrorInlinesException); features.Log(e); } } break; case ResolveSource.Line: // Key has explicit culture and value, use the value if (preferenceIndex == 0 && (features.String != null || features.StringText != null)) { // Up status with source info features.Status.UpResolve(LineStatus.CultureWarningRequestMatchedInvariantCulture); // Up status with culture info LineStatus cultureStatus = culture == null || culture.Name == "" ? LineStatus.CultureOkRequestMatchedInvariantCulture : LineStatus.CultureWarningRequestMatchedInvariantCulture; features.Status.UpCulture(cultureStatus); if (culture == null) { culture = CultureInfo.InvariantCulture; } return(key); } break; } } preferenceIndex++; } // No matching value was found for the requested key and the explicit culture in the key. features.Status.UpCulture(LineStatus.CultureFailedRequestNoMatch); } // Try with cultures from the culture policy CultureInfo[] cultures = features.CulturePolicy?.Cultures; if (culture == null && cultures != null) { try { for (int preferenceIndex = 0; preferenceIndex < cultures.Length; preferenceIndex++) { CultureInfo ci = cultures[preferenceIndex]; // Has already been tested above? Skip if (ci == culture) { continue; } ILine key_with_culture = key.Culture(ci); foreach (var stage in ResolveSequence) { switch (stage) { // Try asset case ResolveSource.Asset: for (int i = 0; i < features.Assets.Count; i++) { try { IAsset asset = features.Assets[i]; if ((line = asset.GetLine(key_with_culture)) != null) { culture = ci; // Up status with source info features.Status.UpResolve(LineStatus.ResolveOkFromAsset); // Up status with culture info LineStatus cultureStatus = preferenceIndex == 0 // Request matched with first preference ? (ci.Name == "" ? LineStatus.CultureOkCulturePolicyMatchedInvariantCulture : ci.IsNeutralCulture ? LineStatus.CultureOkCulturePolicyMatchedLanguage : LineStatus.CultureOkCulturePolicyMatchedLanguageAndRegion) // Request matched with a fallback preference : (ci.Name == "" ? Localization.LineStatus.CultureErrorCulturePolicyMatchedInvariantCulture : ci.IsNeutralCulture ? LineStatus.CultureWarningCulturePolicyMatchedLanguage : Localization.LineStatus.CultureWarningCulturePolicyMatchedLanguageAndRegion); features.Status.UpCulture(cultureStatus); features.ScanFeatures(line); return(line); } } catch (Exception e) { features.Status.UpResolve(LineStatus.ResolveErrorAssetException); features.Log(e); } } break; // Try inlines case ResolveSource.Inline: for (int i = 0; i < features.Inlines.Count; i++) { try { if (features.Inlines[i].TryGetValue(key_with_culture, out line)) { culture = ci; // Up status with source info features.Status.UpResolve(LineStatus.ResolveOkFromInline); // Up status with culture info LineStatus cultureStatus = preferenceIndex == 0 // Request matched with first preference ? (ci.Name == "" ? LineStatus.CultureOkCulturePolicyMatchedInvariantCulture : ci.IsNeutralCulture ? LineStatus.CultureOkCulturePolicyMatchedLanguage : LineStatus.CultureOkCulturePolicyMatchedLanguageAndRegion) // Request matched with a fallback preference : (ci.Name == "" ? Localization.LineStatus.CultureErrorCulturePolicyMatchedInvariantCulture : ci.IsNeutralCulture ? LineStatus.CultureWarningCulturePolicyMatchedLanguage : Localization.LineStatus.CultureWarningCulturePolicyMatchedLanguageAndRegion); features.Status.UpCulture(cultureStatus); features.ScanFeatures(line); return(line); } } catch (Exception e) { features.Status.UpResolve(LineStatus.ResolveErrorInlinesException); features.Log(e); } } break; case ResolveSource.Line: if ((features.String != null || features.StringText != null) && ci.Equals(features.Culture)) { culture = ci; // Up status with source info features.Status.UpResolve(LineStatus.ResolveOkFromLine); // Up status with culture info LineStatus cultureStatus = preferenceIndex == 0 // Request matched with first preference ? (ci.Name == "" ? LineStatus.CultureOkCulturePolicyMatchedInvariantCulture : ci.IsNeutralCulture ? LineStatus.CultureOkCulturePolicyMatchedLanguage : LineStatus.CultureOkCulturePolicyMatchedLanguageAndRegion) // Request matched with a fallback preference : (ci.Name == "" ? Localization.LineStatus.CultureErrorCulturePolicyMatchedInvariantCulture : ci.IsNeutralCulture ? LineStatus.CultureWarningCulturePolicyMatchedLanguage : Localization.LineStatus.CultureWarningCulturePolicyMatchedLanguageAndRegion); features.Status.UpCulture(cultureStatus); return(key); } break; } } } } catch (Exception e) { features.Status.UpResolve(LineStatus.CultureErrorCulturePolicyException); features.Log(e); } } // No request culture - try this _after_ culture policy if (culture == null) { foreach (var stage in ResolveSequence) { switch (stage) { // Try asset case ResolveSource.Asset: for (int i = 0; i < features.Assets.Count; i++) { try { IAsset asset = features.Assets[i]; if ((line = asset.GetLine(key)) != null) { // Up status with source info features.Status.UpResolve(LineStatus.ResolveOkFromAsset); line.TryGetCultureInfo(out culture); if (culture == null) { culture = CultureInfo.InvariantCulture; } // Up status with culture info LineStatus cultureStatus = culture.Name == "" ? LineStatus.CultureOkMatchedInvariantCulture : culture.IsNeutralCulture ? LineStatus.CultureOkMatchedLanguage : LineStatus.CultureOkMatchedLanguageAndRegion; features.Status.UpCulture(cultureStatus); features.ScanFeatures(line); return(line); } } catch (Exception e) { features.Status.UpResolve(LineStatus.ResolveErrorAssetException); features.Log(e); } } break; // Try inlines case ResolveSource.Inline: for (int i = 0; i < features.Inlines.Count; i++) { try { if (features.Inlines[i].TryGetValue(key, out line)) { // Up status with source info features.Status.UpResolve(LineStatus.ResolveOkFromInline); line.TryGetCultureInfo(out culture); if (culture == null) { culture = CultureInfo.InvariantCulture; } // Up status with culture info LineStatus cultureStatus = culture.Name == "" ? LineStatus.CultureOkMatchedInvariantCulture : culture.IsNeutralCulture ? LineStatus.CultureOkMatchedLanguage : LineStatus.CultureOkMatchedLanguageAndRegion; features.Status.UpCulture(cultureStatus); features.ScanFeatures(line); return(line); } } catch (Exception e) { features.Status.UpResolve(LineStatus.ResolveErrorInlinesException); features.Log(e); } } break; case ResolveSource.Line: // Key has explicit culture and value, use the value if (features.String != null || features.StringText != null) { // Up status with source info features.Status.UpResolve(LineStatus.ResolveOkFromLine); if (culture == null) { culture = CultureInfo.InvariantCulture; } LineStatus cultureStatus = LineStatus.CultureOkMatchedInvariantCulture; features.Status.UpCulture(cultureStatus); return(key); } break; } } } // No matching value was found for the requested key and the explicit culture in the key. features.Status.UpCulture(cultures != null ? LineStatus.CultureFailedCulturePolicyNoMatch : LineStatus.CultureFailedRequestNoMatch); // No match features.Status.UpResolve(LineStatus.ResolveErrorNoMatch); culture = null; return(null); } catch (Exception e) { // Uncaptured error features.Status.UpResolve(LineStatus.ResolveError); features.Log(e); culture = null; return(null); } }
/// <summary> /// Resolve <paramref name="key"/> into <see cref="IString"/>, but without applying format arguments. /// /// If the <see cref="IString"/> contains plural categories, then matches into the applicable plurality case. /// </summary> /// <param name="key"></param> /// <returns>format string</returns> public IString ResolveFormatString(ILine key) { // Extract parameters from line LineFeatures features = new LineFeatures { Resolvers = Resolvers }; // Scan features try { features.ScanFeatures(key); } catch (Exception e) { features.Log(e); features.Status.UpResolve(LineStatus.ResolveFailedException); return(new StatusString(null, features.Status)); } // Resolve key to line CultureInfo culture = features.Culture; ILine line = ResolveKeyToLine(key, ref features, ref culture); // No line or value if (line == null || !features.HasValue) { features.Status.UpResolve(LineStatus.ResolveFailedNoValue); LineString str = new LineString(key, (Exception)null, features.Status); features.Log(str); return(new StatusString(null, features.Status)); } // Parse value IString value = features.EffectiveString; features.Status.Up(value.Status); // Value has error if (value.Parts == null || value.Status.Failed()) { LineString str = new LineString(key, (Exception)null, features.Status); features.Log(str); return(new StatusString(null, features.Status)); } // Plural Rules if (value.HasPluralRules()) { if (features.PluralRules != null) { // Evaluate expressions in placeholders into strings StructList12 <string> placeholder_values = new StructList12 <string>(); CultureInfo culture_for_format = features.Culture; if (culture_for_format == null && features.CulturePolicy != null) { CultureInfo[] cultures = features.CulturePolicy.Cultures; if (cultures != null && cultures.Length > 0) { culture_for_format = cultures[0]; } } if (culture_for_format == null) { culture_for_format = CultureInfo.InvariantCulture; } EvaluatePlaceholderValues(key, line, null, value.Placeholders, ref features, ref placeholder_values, culture_for_format); // Create permutation configuration PluralCasePermutations permutations = new PluralCasePermutations(line); for (int i = 0; i < value.Placeholders.Length; i++) { // Get placeholder IPlaceholder placeholder = value.Placeholders[i]; // No plural category in this placeholder if (placeholder.PluralCategory == null) { continue; } // Placeholder value after evaluation string ph_value = placeholder_values[i]; // Placeholder evaluated value IPluralNumber placeholderValue = ph_value == null ? DecimalNumber.Empty : new DecimalNumber.Text(ph_value?.ToString(), culture); // Add placeholder to permutation configuration permutations.AddPlaceholder(placeholder, placeholderValue, features.PluralRules, culture?.Name ?? ""); } // Find first value that matches permutations features.CulturePolicy = null; features.String = null; features.StringText = null; for (int i = 0; i < permutations.Count - 1; i++) { // Create key with plurality cases ILine key_with_plurality = permutations[i]; // Search line with the key ILine line_for_plurality_arguments = ResolveKeyToLine(key_with_plurality, ref features, ref culture); // Got no match if (line_for_plurality_arguments == null) { continue; } // Parse value IString value_for_plurality = line_for_plurality_arguments.GetString(Resolvers); // Add status from parsing the value features.Status.Up(value_for_plurality.Status); // Value has error if (value_for_plurality.Parts == null || value_for_plurality.Status.Failed()) { LineString str = new LineString(key, (Exception)null, features.Status); features.Log(str); return(new StatusString(null, features.Status)); } // Return with match features.Status.UpPlurality(LineStatus.PluralityOkMatched); // Update status codes features.Status.Up(value_for_plurality.Status); // Return values value = value_for_plurality; line = line_for_plurality_arguments; break; } } else { // Plural rules were not found features.Status.Up(LineStatus.PluralityErrorRulesNotFound); } } else { // Plurality feature was not used. features.Status.UpPlurality(LineStatus.PluralityOkNotUsed); } return(value); }
/// <summary> /// Resolve <paramref name="key"/> into <see cref="LineString"/> with format arguments applied. /// </summary> /// <param name="key"></param> /// <returns></returns> public LineString ResolveString(ILine key) { // Extract parameters from line LineFeatures features = new LineFeatures { Resolvers = Resolvers }; // Scan features try { features.ScanFeatures(key); } catch (Exception e) { features.Log(e); features.Status.UpResolve(LineStatus.ResolveFailedException); return(new LineString(key, e, features.Status)); } try { // Resolve key to line CultureInfo culture = features.Culture; ILine line = ResolveKeyToLine(key, ref features, ref culture); // No line or value if (line == null || !features.HasValue) { features.Status.UpResolve(LineStatus.ResolveFailedNoValue); LineString str = new LineString(key, (Exception)null, features.Status); features.Log(str); return(str); } // Parse value IString value = features.EffectiveString; features.Status.Up(value.Status); // Value has error if (value.Parts == null || value.Status.Failed()) { LineString str = new LineString(key, (Exception)null, features.Status); features.Log(str); return(str); } // Evaluate expressions in placeholders into strings StructList12 <string> placeholder_values = new StructList12 <string>(); CultureInfo culture_for_format = features.Culture; if (culture_for_format == null && features.CulturePolicy != null) { CultureInfo[] cultures = features.CulturePolicy.Cultures; if (cultures != null && cultures.Length > 0) { culture_for_format = cultures[0]; } } if (culture_for_format == null) { culture_for_format = CultureInfo.InvariantCulture; } EvaluatePlaceholderValues(key, line, null, value.Placeholders, ref features, ref placeholder_values, culture_for_format); // Plural Rules if (value.HasPluralRules()) { if (features.PluralRules != null) { // Create permutation configuration PluralCasePermutations permutations = new PluralCasePermutations(line); for (int i = 0; i < value.Placeholders.Length; i++) { // Get placeholder IPlaceholder placeholder = value.Placeholders[i]; // No plural category in this placeholder if (placeholder.PluralCategory == null) { continue; } // Placeholder value after evaluation string ph_value = placeholder_values[i]; // Placeholder evaluated value IPluralNumber placeholderValue = ph_value == null ? DecimalNumber.Empty : new DecimalNumber.Text(ph_value?.ToString(), culture); // Add placeholder to permutation configuration permutations.AddPlaceholder(placeholder, placeholderValue, features.PluralRules, culture?.Name ?? ""); } if (permutations.ArgumentCount <= MaxPluralArguments) { // Find first value that matches permutations features.CulturePolicy = null; features.String = null; features.StringText = null; for (int i = 0; i < permutations.Count - 1; i++) { // Create key with plurality cases ILine key_with_plurality = permutations[i]; // Search line with the key ILine line_for_plurality_arguments = ResolveKeyToLine(key_with_plurality, ref features, ref culture); // Got no match if (line_for_plurality_arguments == null) { continue; } // Scan value try { features.ScanValueFeature(line_for_plurality_arguments); } catch (Exception e) { features.Log(e); features.Status.Up(LineStatus.FailedUnknownReason); return(new LineString(key, e, features.Status)); } // Parse value IString value_for_plurality = features.EffectiveString; // Add status from parsing the value features.Status.Up(value_for_plurality.Status); // Value has error if (value_for_plurality.Parts == null || value_for_plurality.Status.Failed()) { LineString str = new LineString(key, (Exception)null, features.Status); features.Log(str); return(str); } // Return with match features.Status.UpPlurality(LineStatus.PluralityOkMatched); // Evaluate placeholders again if (!EqualPlaceholders(value, value_for_plurality)) { placeholder_values.Clear(); EvaluatePlaceholderValues(key, line, line_for_plurality_arguments, value_for_plurality.Placeholders, ref features, ref placeholder_values, culture); } // Update status codes features.Status.Up(value_for_plurality.Status); // Return values value = value_for_plurality; line = line_for_plurality_arguments; break; } } else { features.Status.UpPlaceholder(LineStatus.PluralityErrorMaxPluralArgumentsExceeded); } } else { // Plural rules were not found features.Status.Up(LineStatus.PluralityErrorRulesNotFound); } } else { // Plurality feature was not used. features.Status.UpPlurality(LineStatus.PluralityOkNotUsed); } // Put string together string text = null; if (value == null || value.Parts == null) { text = null; } // Only one part else if (value.Parts.Length == 1) { if (value.Parts[0].Kind == StringPartKind.Text) { text = value.Parts[0].Text; features.Status.UpStringFormat(LineStatus.StringFormatOkString); } else if (value.Parts[0].Kind == StringPartKind.Placeholder) { text = placeholder_values[0]; features.Status.UpStringFormat(LineStatus.StringFormatOkString); } } // Compile multiple parts else { // Calculate length int length = 0; for (int i = 0; i < value.Parts.Length; i++) { IStringPart part = value.Parts[i]; string partText = part.Kind switch { StringPartKind.Text => part.Text, StringPartKind.Placeholder => placeholder_values[((IPlaceholder)part).PlaceholderIndex], _ => null }; if (partText != null) { length += partText.Length; } } // Copy characters char[] arr = new char[length]; int ix = 0; for (int i = 0; i < value.Parts.Length; i++) { IStringPart part = value.Parts[i]; string str = part.Kind switch { StringPartKind.Text => part.Text, StringPartKind.Placeholder => placeholder_values[((IPlaceholder)part).PlaceholderIndex], _ => null }; if (str != null) { str.CopyTo(0, arr, ix, str.Length); ix += str.Length; } } // String text = new string(arr); features.Status.UpStringFormat(LineStatus.StringFormatOkString); } // Create result LineString result = new LineString(key, text, features.Status); // Log features.Log(result); // Return return(result); } catch (Exception e) { // Capture unexpected error features.Log(e); features.Status.UpResolve(LineStatus.ResolveFailedException); LineString lineString = new LineString(key, e, features.Status); features.Log(lineString); return(lineString); } }