public void LoadFromAssembly(Assembly assembly, string location, Parser parser) { if (parser == null) { throw new ArgumentNullException("parser"); } if (assembly == null) { throw new ArgumentNullException("assembly"); } if (string.IsNullOrEmpty(location)) { throw new ArgumentException("The value cannot be empty", "location"); } // assembly embedded files use a dot as folder separator var location1 = assembly.GetName().Name + "." + location.Replace("/", ".").Replace("\\", "."); var traceMessage = new StringBuilder(); // find files that match the pattern var files = assembly.GetManifestResourceNames() .Where(x => x.StartsWith(location1, StringComparison.OrdinalIgnoreCase)) .ToArray(); // determine the roles of each file for (int i = 0; i < files.Length; i++) { var file = files[i]; bool isDefault = false; // decompose remaining of file name by splitting on dots var parts = file.Substring(location1.Length).Split('.'); var tags = new List <string>(); var dimensions = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase); for (int j = 0; j < parts.Length; j++) { var part = parts[parts.Length - j - 1]; if (j == 0) { if (fileExtensions.Any(x => x.Equals(part, StringComparison.OrdinalIgnoreCase))) { // this .md/.txt file } else { // bad file extension traceMessage.Append("File "); traceMessage.Append(file); traceMessage.AppendLine(" has an unknown extension."); goto nextFile; } } else if ("Default".Equals(part, StringComparison.OrdinalIgnoreCase)) { isDefault = true; } else { var dashIndex = part.IndexOf('-'); if (dashIndex > 0 && dashIndex < (part.Length - 1)) { // part is Dimension+Value dimensions.Add(part.Substring(0, dashIndex), part.Substring(dashIndex + 1)); } else { // part is not Dimension+Value tags.Add(part); traceMessage.Append("File "); traceMessage.Append(file); traceMessage.AppendLine(" has a file name part that does not look like a `Dimension-Value`."); } } } // determine standard culture var resource = new ResourceFile(tags); resource.Dimensions = dimensions; if (dimensions.ContainsKey("L") && dimensions.ContainsKey("R")) { try { var culture = new CultureInfo(dimensions["L"] + "-" + dimensions["R"]); resource.Culture = culture; } catch (CultureNotFoundException) { resource.Culture = CultureInfo.InvariantCulture; } } else if (dimensions.ContainsKey("L")) { try { var culture = new CultureInfo(dimensions["L"]); resource.Culture = culture; } catch (CultureNotFoundException) { resource.Culture = CultureInfo.InvariantCulture; } } // open and parse file Stream stream; try { stream = assembly.GetManifestResourceStream(file); } catch (Exception ex) { trace.Write(traceMessage); throw new MarkalizeException("Failed to open file \"" + file + "\": " + ex.Message, ex); } using (var reader = new StreamReader(stream, Encoding.UTF8)) { try { parser.Parse(reader, resource); } catch (MarkalizeException ex) { trace.Write(traceMessage); throw new MarkalizeException("Failed to parse file \"" + file + "\": " + ex.Message, ex); } catch (Exception ex) { trace.Write(traceMessage); throw new MarkalizeException("Failed to parse file \"" + file + "\": " + ex.Message, ex); } } if (isDefault) { this.resources.Insert(0, resource); } else { this.resources.Add(resource); } this.keys = null; nextFile :; } trace.Write(traceMessage); }
public void Parse(TextReader reader, ResourceFile resource) { if (reader == null) { throw new ArgumentNullException("reader"); } if (resource == null) { throw new ArgumentNullException("resource"); } string line = null, nextLine = null, key = null, genre = null, lineEnding = null, nextLineEnding; int skip = 0, number = 0, quoteCount = 0; var prefixes = new List <string>(5); var prefixesLevel = new List <byte>(5); bool isInFencedCode = false, inQuotedValue = false, nextLineInContinuation = false, isVerbatim = false; var lineBuilder = new StringBuilder(); StringBuilder sb = null; Func <bool> isEndOfStream; if (reader is StreamReader) { var streamReader = (StreamReader)reader; isEndOfStream = new Func <bool>(() => streamReader.EndOfStream); } else { isEndOfStream = new Func <bool>(() => reader.Peek() < 0); } // loop on all lines (!isEndOfStream()) // make one extra turn for the last line (line != null)) while (!isEndOfStream() || line != null) { if (line == null) { // first time in loop line = string.Empty; lineEnding = string.Empty; } nextLine = ReadLine(reader, out nextLineEnding); if (skip > 0) { // don't do anything skip--; } else if (inQuotedValue) { var rawValue = line; var valueTrimmed = rawValue.TrimStart(); int leftTrim = rawValue.Length - valueTrimmed.Length; valueTrimmed = valueTrimmed.TrimEnd(); int endTrim = rawValue.Length - valueTrimmed.Length - leftTrim; bool putLineFeed = true; if (valueTrimmed[valueTrimmed.Length - 1] == '\\') { // line ends with \ valueTrimmed = valueTrimmed.Substring(0, valueTrimmed.Length - 1); putLineFeed = false; } if (valueTrimmed.Length > 0 && valueTrimmed[valueTrimmed.Length - 1] == '"' && valueTrimmed.Reverse().Take(quoteCount).All(c => c == '"')) { // line ends with '"' // end of value inQuotedValue = false; sb.Append(valueTrimmed, 0, valueTrimmed.Length - quoteCount); resource.Set(key, number, genre, sb.ToString()); sb = null; } else { // value continues sb.Append(valueTrimmed); sb.Append(rawValue, rawValue.Length - endTrim, endTrim); // cancel TrimEnd sb.AppendIf(this.NormalizeCRFL(lineEnding), putLineFeed); } } else if (nextLineInContinuation) { var valueTrimmed = line.Trim(); if (valueTrimmed.Length > 0 && valueTrimmed[valueTrimmed.Length - 1] == '\\') { // line ends with '\' nextLineInContinuation = true; sb.Append(valueTrimmed, 0, valueTrimmed.Length - 1); } else { // end of value nextLineInContinuation = false; sb.Append(valueTrimmed); resource.Set(key, number, genre, sb.ToString()); sb = null; } } else if (line.StartsWith("```") || line.StartsWith("~~~")) { if (isInFencedCode) { isInFencedCode = false; } else { isInFencedCode = true; } } else if (string.IsNullOrEmpty(line) || isInFencedCode) { // don't do anything } else if (IsTitle1Row(nextLine)) { // this is a title! skip++; var prefix = FindEnclosedPrefixInTitle(line) ?? ExtractKey(line, out string _, out int __, out string ___); prefixes.Clear(); prefixesLevel.Clear(); prefixes.Add(prefix); prefixesLevel.Add(1); } else if (IsTitle2Row(nextLine)) { // this is a title! skip++; var prefix = FindEnclosedPrefixInTitle(line) ?? ExtractKey(line, out string _, out int __, out string ___); while (prefixesLevel.Count(x => x >= 2) > 0) // keep title1 prefix { var index = prefixes.Count - 1; prefixes.RemoveAt(index); prefixesLevel.RemoveAt(index); } prefixes.Add(prefix); prefixesLevel.Add(2); } else if (IsResetTitleRow(line)) { prefixes.Clear(); prefixesLevel.Clear(); } else { // this line is a key+value key = ExtractKey(line, out string rawValue, out number, out genre); if (key != null && prefixes.Count > 0) { key = string.Concat(prefixes) + key; } if (key == null) { // strange text on this line } else if (string.IsNullOrEmpty(rawValue)) { resource.Set(key, number, genre, rawValue); } else { var valueTrimmed = rawValue.TrimStart(); int leftTrim = rawValue.Length - valueTrimmed.Length; valueTrimmed = valueTrimmed.TrimEnd(); int endTrim = rawValue.Length - valueTrimmed.Length - leftTrim; char fc1 = '\0', fc2 = '\0', fc3 = '\0', lc1 = '\0', lc2 = '\0'; if (valueTrimmed.Length >= 1) { fc1 = valueTrimmed[0]; lc1 = valueTrimmed[valueTrimmed.Length - 1]; } if (valueTrimmed.Length >= 2) { fc2 = valueTrimmed[1]; lc2 = valueTrimmed[valueTrimmed.Length - 2]; } if (valueTrimmed.Length >= 3) { fc3 = valueTrimmed[2]; } if (fc1 == '"' || (fc1 == '@' && fc2 == '"')) { // value starts with double quotes isVerbatim = fc1 == '@'; quoteCount = valueTrimmed.SkipWhile((c, i) => c == '@' && i == 0).TakeWhile(c => c == '"').Count(); valueTrimmed = valueTrimmed.Substring(quoteCount + (isVerbatim ? 1 : 0)); leftTrim += quoteCount + (isVerbatim ? 1 : 0); if (valueTrimmed.Length > 0 && valueTrimmed[valueTrimmed.Length - 1] == '"' && valueTrimmed.Reverse().Take(quoteCount).All(c => c == '"')) { // value ends with double quotes valueTrimmed = valueTrimmed.Substring(0, valueTrimmed.Length - quoteCount); resource.Set(key, number, genre, valueTrimmed); } else { inQuotedValue = true; sb = new StringBuilder(); bool putLineFeed = true; if (valueTrimmed[valueTrimmed.Length - 1] == '\\') { // line ends with \ valueTrimmed = valueTrimmed.Substring(0, valueTrimmed.Length - 1); putLineFeed = false; } sb.Append(valueTrimmed); sb.Append(rawValue, rawValue.Length - endTrim, endTrim); // cancel TrimEnd sb.AppendIf(this.NormalizeCRFL(lineEnding), putLineFeed); ////if (valueTrimmed[valueTrimmed.Length - 1] == '\\') ////{ //// // quoted line ends with backslash //// valueTrimmed = valueTrimmed.Remove(valueTrimmed.Length - 2); //// sb.Append(valueTrimmed); //// sb.Append(rawValue, rawValue.Length - endTrim, endTrim); ////} ////else ////{ //// sb.Append(valueTrimmed); //// sb.Append(rawValue, rawValue.Length - endTrim, endTrim); //// sb.Append(this.PreserveNewLines ? lineEnding : Environment.NewLine); ////} } } else if (valueTrimmed[valueTrimmed.Length - 1] == '\\') { // simple value ends with backslash nextLineInContinuation = true; sb = new StringBuilder(); sb.Append(valueTrimmed.Substring(0, valueTrimmed.Length - 1)); } else { // simple value is a one-liner resource.Set(key, number, genre, valueTrimmed); } } } line = nextLine; lineEnding = nextLineEnding; } // this can't work with StreamReader: tne last line evaporates ////if (!string.IsNullOrEmpty(line)) ////{ //// throw new MarkalizeException("File does not seem to end with an empty line"); ////} }