/// <summary> /// Duplicate msgids encountered - Abort if the msgstr contains a reference, as /// duplicate msgids are now supported yet and will result in the msgstr not being expanded. /// </summary> void TestDuplicateEntry(MsgInfo duplicateEntry) { if (GetFirstReference(duplicateEntry.Msgstr_Value, 0) != null) { throw new LineException(duplicateEntry.Msgstr_Info, "Duplicate msgid \"" + duplicateEntry.Msgid_Value + "\" encountered - Aborting because this is not supported yet and the msgstr contains a reference which popp will fail to expand."); } else { ErrorEncountered(duplicateEntry.Msgstr_Info, "Duplicate msgid \"" + duplicateEntry.Msgid_Value + "\" encountered (non-fatal)."); } }
/// <summary> /// Parse the PO formatted file into a dictionary of entries. /// </summary> Dictionary<string/*MsgInfo.UniqueID*/, MsgInfo> BuildMsgInfoDictionary(IEnumerable<LineInfo> lines) { Dictionary<string/*MsgInfo.UniqueID*/, MsgInfo> result = new Dictionary<string, MsgInfo>(); // Add a whitespace entry to the end of the list, so our state machine can rely on // whitespace as an end-of-entry marker. IEnumerable<LineInfo> linesPlusWhitespace = lines.Concat(new LineInfo[]{new LineInfo(LineType.Whitespace, -1, "")}); LineInfoState state = LineInfoState.FinishedEntry; MsgInfo newEntry = new MsgInfo(); foreach (LineInfo line in linesPlusWhitespace) { // The state machine is small enough to do with a switch. // // The .PO formats is roughly like this: // // Whitespace, followed by // [Optional]# Comments, followed by // [Optional]msgctxt, optionally followed by multi-line context string, followed by // msgid, optionally followed by multi-line id string, followed by // [Optional]msgid_plural - I'm not supporting plural forms, followed by // msgstr, optionally followed by multi-line msg string, followed by // [Optional]msgstr[x] - I'm not supporting plural forms, followed by // EOF or Whitespace switch (state) { case LineInfoState.FinishedEntry: // We're looking for the start of the next entry if (line.Type == LineType.Msgid) { state = LineInfoState.AddingMsgid; newEntry.Msgid_Value = ExtractString(line); } else if (line.Type == LineType.Msgctxt) { // msgctxt is optional, but if present it appears before the msgid state = LineInfoState.AddingMsgctxt; newEntry.Msgctxt_Value = ExtractString(line); } else if (line.Type == LineType.Msgstr || line.Type == LineType.StrContinuation) { // An entry can't start with a msgstr or string-continuation ErrorEncountered(line, "Unexpected string or msgstr"); } break; case LineInfoState.AddingMsgctxt: if (line.Type == LineType.StrContinuation) { newEntry.Msgctxt_Value += ExtractString(line); } else if (line.Type == LineType.Msgid) { state = LineInfoState.AddingMsgid; newEntry.Msgid_Value = ExtractString(line); } else { // msgctxt is optional, but if present it appears before the msgid ErrorEncountered(line, "msgid not found after msgctxt"); } break; case LineInfoState.AddingMsgid: if (line.Type == LineType.StrContinuation) { newEntry.Msgid_Value += ExtractString(line); } else if (line.Type == LineType.Msgstr) { state = LineInfoState.AddingMsgstr; newEntry.Msgstr_Value = ExtractString(line); newEntry.Msgstr_Info = line; } else if (line.Type == LineType.Msgid) { // We can't have two msgids in a row! if (line.Line.StartsWith("msgid_plural", true, CultureInfo.InvariantCulture)) { ErrorEncountered(line, "Multiple msgids encountered, PO plural-forms are not currently supported :("); } else { ErrorEncountered(line, "Unexpected msgid"); } } break; case LineInfoState.AddingMsgstr: if (line.Type == LineType.StrContinuation) { newEntry.Msgstr_Value += ExtractString(line); newEntry.Msgstr_LineCount++; } else if (line.Type == LineType.Whitespace) { // We've found the end of the entry state = LineInfoState.FinishedEntry; if (newEntry.IsValid()) { string uniqueID = newEntry.UniqueID(_options.CaseSensitiveIDs); if (result.ContainsKey(uniqueID)) { // Duplicate msgids encountered - Abort if the msgstr contains a reference, as duplicate msgids are now supported yet and will result in the reference not expanding TestDuplicateEntry(newEntry); } else { result.Add(newEntry.UniqueID(_options.CaseSensitiveIDs), newEntry); } } else { ErrorEncountered(line, "[End found of] invalid entry"); } newEntry = new MsgInfo(); } else if (line.Type == LineType.Msgstr) { // Multiple msgstrs encountered, PO plural-forms are not currently supported :( TestPluralForm(line); } else { ErrorEncountered(line, "Unexpected line encountered at end of entry \"" + newEntry.Msgid_Value + "\" (was expecting whitespace)"); } break; } } return result; }
/// <summary> /// Run the preprocessor /// </summary> /// <param name="inputDirectory">Note - can be null</param> /// <returns>errorLevel, or 0 for success</returns> /// <param name="inputDirectory">The directory that contained the source file that was supplied to popp, or null (e.g. if stdin was the source)</param> public int Process(TextReader inputReader, string inputDirectory, TextWriter outputWriter) { int unexpandableReferenceCount = 0; _errorLevel = 0; try { // Build a list of information about each line, expanding any $include statements IEnumerable <LineInfo> lines = BuildListOfLines(inputReader, inputDirectory, true); // Todo: // This is where we'll apply the $if $else etc conditional directives // lines = ApplyConditionalDirectives(lines); // Build a dictionary of translation items Dictionary <string /*MsgInfo.UniqueID*/, MsgInfo> keyValues = BuildMsgInfoDictionary(lines); int expandedReferenceCount; unexpandableReferenceCount = ExpandMsgstrs(keyValues, out expandedReferenceCount); // Write out the file with any adjusted msgstr entries... // build a second dictionary of the msgstrs we are changing, indexed by the linenumber Dictionary <int /*msgstr_lineNumber*/, MsgInfo> alteredMsgstrLines = new Dictionary <int, MsgInfo>(); foreach (MsgInfo msgInfo in keyValues.Values) { if (msgInfo.Msgstr_ContainsChanges) { alteredMsgstrLines.Add(msgInfo.Msgstr_Info.LineNumber, msgInfo); } } // Write the lines to the output file, with any adjusted msgstr entries int linesToSkip = 0; foreach (LineInfo lineInfo in lines) { if (linesToSkip > 0) { // skip over the multiline source strings if we have already written out our own version linesToSkip--; } else { bool replaceLineWithAdjustedMsgstr = false; MsgInfo msgInfo = null; if (alteredMsgstrLines.TryGetValue(lineInfo.LineNumber, out msgInfo)) { // We have our own version of this line if (msgInfo.Msgstr_ContainsChanges) { replaceLineWithAdjustedMsgstr = true; } } // Only change the lines in the file that we need to, so all whitespace and weird user // formatting etc will be preserved. if (replaceLineWithAdjustedMsgstr) { outputWriter.WriteLine("msgstr \"" + msgInfo.Msgstr_Value + "\""); linesToSkip = msgInfo.Msgstr_LineCount; } else { outputWriter.WriteLine(lineInfo.Line); } } } outputWriter.Close(); DisplayInfo("Done - Expanded " + expandedReferenceCount + " references"); if (unexpandableReferenceCount > 0) { DisplayInfo(" Failed to expand " + unexpandableReferenceCount + " references"); } } catch (LineException ex) { ErrorEncountered(ex.LineInfo, ex.Message); _errorLevel = (int)ErrorLevel.FatalError_Internal; } catch (Exception ex) { ErrorEncountered("Unexpected internal error - " + ex); _errorLevel = (int)ErrorLevel.FatalError_Internal; } // If the preprocessing was successful, then return the number of references // we found which could not be expanded, as a negative number (to prevent // confusion with the error codes) if (_errorLevel == 0) { _errorLevel = -unexpandableReferenceCount; } return(_errorLevel); }
/// <summary> /// Parse the PO formatted file into a dictionary of entries. /// </summary> Dictionary <string /*MsgInfo.UniqueID*/, MsgInfo> BuildMsgInfoDictionary(IEnumerable <LineInfo> lines) { Dictionary <string /*MsgInfo.UniqueID*/, MsgInfo> result = new Dictionary <string, MsgInfo>(); // Add a whitespace entry to the end of the list, so our state machine can rely on // whitespace as an end-of-entry marker. IEnumerable <LineInfo> linesPlusWhitespace = lines.Concat(new LineInfo[] { new LineInfo(LineType.Whitespace, -1, "") }); LineInfoState state = LineInfoState.FinishedEntry; MsgInfo newEntry = new MsgInfo(); foreach (LineInfo line in linesPlusWhitespace) { // The state machine is small enough to do with a switch. // // The .PO formats is roughly like this: // // Whitespace, followed by // [Optional]# Comments, followed by // [Optional]msgctxt, optionally followed by multi-line context string, followed by // msgid, optionally followed by multi-line id string, followed by // [Optional]msgid_plural - I'm not supporting plural forms, followed by // msgstr, optionally followed by multi-line msg string, followed by // [Optional]msgstr[x] - I'm not supporting plural forms, followed by // EOF or Whitespace switch (state) { case LineInfoState.FinishedEntry: // We're looking for the start of the next entry if (line.Type == LineType.Msgid) { state = LineInfoState.AddingMsgid; newEntry.Msgid_Value = ExtractString(line); } else if (line.Type == LineType.Msgctxt) { // msgctxt is optional, but if present it appears before the msgid state = LineInfoState.AddingMsgctxt; newEntry.Msgctxt_Value = ExtractString(line); } else if (line.Type == LineType.Msgstr || line.Type == LineType.StrContinuation) { // An entry can't start with a msgstr or string-continuation ErrorEncountered(line, "Unexpected string or msgstr"); } break; case LineInfoState.AddingMsgctxt: if (line.Type == LineType.StrContinuation) { newEntry.Msgctxt_Value += ExtractString(line); } else if (line.Type == LineType.Msgid) { state = LineInfoState.AddingMsgid; newEntry.Msgid_Value = ExtractString(line); } else { // msgctxt is optional, but if present it appears before the msgid ErrorEncountered(line, "msgid not found after msgctxt"); } break; case LineInfoState.AddingMsgid: if (line.Type == LineType.StrContinuation) { newEntry.Msgid_Value += ExtractString(line); } else if (line.Type == LineType.Msgstr) { state = LineInfoState.AddingMsgstr; newEntry.Msgstr_Value = ExtractString(line); newEntry.Msgstr_Info = line; } else if (line.Type == LineType.Msgid) { // We can't have two msgids in a row! if (line.Line.StartsWith("msgid_plural", true, CultureInfo.InvariantCulture)) { ErrorEncountered(line, "Multiple msgids encountered, PO plural-forms are not currently supported :("); } else { ErrorEncountered(line, "Unexpected msgid"); } } break; case LineInfoState.AddingMsgstr: if (line.Type == LineType.StrContinuation) { newEntry.Msgstr_Value += ExtractString(line); newEntry.Msgstr_LineCount++; } else if (line.Type == LineType.Whitespace) { // We've found the end of the entry state = LineInfoState.FinishedEntry; if (newEntry.IsValid()) { string uniqueID = newEntry.UniqueID(_options.CaseSensitiveIDs); if (result.ContainsKey(uniqueID)) { // Duplicate msgids encountered - Abort if the msgstr contains a reference, as duplicate msgids are now supported yet and will result in the reference not expanding TestDuplicateEntry(newEntry); } else { result.Add(newEntry.UniqueID(_options.CaseSensitiveIDs), newEntry); } } else { ErrorEncountered(line, "[End found of] invalid entry"); } newEntry = new MsgInfo(); } else if (line.Type == LineType.Msgstr) { // Multiple msgstrs encountered, PO plural-forms are not currently supported :( TestPluralForm(line); } else { ErrorEncountered(line, "Unexpected line encountered at end of entry \"" + newEntry.Msgid_Value + "\" (was expecting whitespace)"); } break; } } return(result); }