/// <summary> /// Parse string /// </summary> protected void Build() { StructList8 <IStringPart> parts = new StructList8 <IStringPart>(); // Create parser CSharpFormat.CSharpParser parser = new CSharpFormat.CSharpParser(this); // Read parts while (parser.HasMore) { IStringPart part = parser.ReadNext(); if (part != null) { parts.Add(part); } } // Unify text parts for (int i = 1; i < parts.Count;) { if (parts[i - 1] is TextPart left && parts[i] is TextPart right) { parts[i - 1] = TextPart.Unify(left, right); parts.RemoveAt(i); }
/// <summary> /// Read next character /// </summary> /// <returns>possible part</returns> public IStringPart ReadNext() { while (HasMore) { // Move to next index i++; // Beyond end if (i >= str.Length) { return(CompletePart(str.Length)); } // End escape if (escaped) { escaped = false; continue; } // Read char char ch = str[i]; // Begin escape if (ch == '\\') { escaped = true; continue; } // Open brace if (ch == '{') { // Start argument if (state == ParserState.Text) { // Complate previous part, and reset state IStringPart part = CompletePart(i); // Start argument state = ParserState.ArgumentStart; // return(part); } else { // Already in argument format and got unexpected unescaped '{' status = LineStatus.StringFormatErrorMalformed; // continue; } } // Close brace if (ch == '}') { // End argument if (state != ParserState.Text) { // End argument state = ParserState.ArgumentEnd; // Complete previous part, and reset state IStringPart part = CompletePart(i + 1); // return(part); } else { // In text state and got unexpected unescaped '}' //status = LocalizationStatus.FormatErrorMalformed; // Go on continue; } } // Nothing further for text part if (state == ParserState.Text) { continue; } // At ArgumentStart, choose next state if (state == ParserState.ArgumentStart) { // index char if (ch >= '0' && ch <= '9') { if (indexStartIx < 0) { indexStartIx = i; } indexEndIx = i + 1; state = ParserState.Index; } else // function char { if (pluralCategoryStartIx < 0) { pluralCategoryStartIx = i; } pluralCategoryEndIx = i + 1; state = ParserState.PluralCategory; } continue; } // At PluralCategory state if (state == ParserState.PluralCategory) { // Change to Index state if (ch == ':') { state = ParserState.Index; } else // Move indices { if (pluralCategoryStartIx < 0) { pluralCategoryStartIx = i; } pluralCategoryEndIx = i + 1; } continue; } // At Index state if (state == ParserState.Index) { // Move indices if (ch >= '0' && ch <= '9') { if (indexStartIx < 0) { indexStartIx = i; } indexEndIx = i + 1; continue; } // Change to Alignment state if (ch == ',') { state = ParserState.Alignment; continue; } // Change to Format state if (ch == ':') { state = ParserState.Format; continue; } // Unexpected character status = LineStatus.StringFormatErrorMalformed; return(CompletePart(i)); } // At Alignment state if (state == ParserState.Alignment) { // Move indices if ((ch >= '0' && ch <= '9') || (ch == '-')) { if (alignmentStartIx < 0) { alignmentStartIx = i; } alignmentEndIx = i + 1; continue; } // Change to Format state if (ch == ':') { state = ParserState.Format; continue; } // Unexpected character status = LineStatus.StringFormatErrorMalformed; return(CompletePart(i)); } // At Format state if (state == ParserState.Format) { // Move indices if (formatStartIx < 0) { formatStartIx = i; } formatEndIx = i + 1; continue; } } return(null); }
/// <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); } }