/// <summary> /// Parses a GNU MO file from the given stream and loads all available data. /// </summary> /// <remarks> /// http://www.gnu.org/software/gettext/manual/html_node/MO-Files.html /// </remarks> /// <param name="stream">Stream that contain binary data in the MO file format</param> public void Parse(Stream stream) { this._Init(); Trace.WriteLine("Trying to parse a MO file stream...", "NGettext"); if (stream == null || stream.Length < 20) { throw new ArgumentException("Stream can not be null of less than 20 bytes long."); } var reader = new BinaryReader(new ReadOnlyStreamWrapper(stream)); try { var magicNumber = reader.ReadUInt32(); if (magicNumber != MO_FILE_MAGIC) { // System.IO.BinaryReader does not respect machine endianness and always uses little-endian // So we need to detect and read big-endian files by ourselves if (_ReverseBytes(magicNumber) == MO_FILE_MAGIC) { Trace.WriteLine("Big Endian file detected. Switching readers...", "NGettext"); this.IsBigEndian = true; #if DNXCORE50 reader.Dispose(); #else reader.Close(); #endif reader = new BigEndianBinaryReader(new ReadOnlyStreamWrapper(stream)); } else { throw new ArgumentException("Invalid stream: can not find MO file magic number."); } } var revision = reader.ReadInt32(); this.FormatRevision = new Version(revision >> 16, revision & 0xffff); Trace.WriteLine(String.Format("MO File Revision: {0}.{1}.", this.FormatRevision.Major, this.FormatRevision.Minor), "NGettext"); if (this.FormatRevision.Major > MAX_SUPPORTED_VERSION) { throw new Exception(String.Format("Unsupported MO file major revision: {0}.", this.FormatRevision.Major)); } var stringCount = reader.ReadInt32(); var originalTableOffset = reader.ReadInt32(); var translationTableOffset = reader.ReadInt32(); // We don't support hash tables and system dependent segments. Trace.WriteLine(String.Format("MO File contains {0} strings.", stringCount), "NGettext"); var originalTable = new StringOffsetTable[stringCount]; var translationTable = new StringOffsetTable[stringCount]; Trace.WriteLine(String.Format("Trying to parse strings using encoding \"{0}\"...", this.Encoding), "NGettext"); reader.BaseStream.Seek(originalTableOffset, SeekOrigin.Begin); for (int i = 0; i < stringCount; i++) { originalTable[i].Length = reader.ReadInt32(); originalTable[i].Offset = reader.ReadInt32(); } reader.BaseStream.Seek(translationTableOffset, SeekOrigin.Begin); for (int i = 0; i < stringCount; i++) { translationTable[i].Length = reader.ReadInt32(); translationTable[i].Offset = reader.ReadInt32(); } for (int i = 0; i < stringCount; i++) { var originalStrings = this._ReadStrings(reader, originalTable[i].Offset, originalTable[i].Length); var translatedStrings = this._ReadStrings(reader, translationTable[i].Offset, translationTable[i].Length); if (originalStrings.Length == 0 || translatedStrings.Length == 0) continue; if (originalStrings[0].Length == 0) { // MO file metadata processing foreach (var headerText in translatedStrings[0].Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries)) { var header = headerText.Split(new[] { ':' }, 2); if (header.Length == 2) { this.Headers.Add(header[0], header[1].Trim()); } } if (this.DetectEncoding && this.Headers.ContainsKey("Content-Type")) { try { var contentType = new ContentType(this.Headers["Content-Type"]); if (!String.IsNullOrEmpty(contentType.CharSet)) { this.Encoding = Encoding.GetEncoding(contentType.CharSet); Trace.WriteLine(String.Format("File encoding switched to \"{0}\" (\"{1}\" requested).", this.Encoding, contentType.CharSet), "NGettext"); } } catch (Exception exception) { Trace.WriteLine(String.Format("Unable to change parser encoding using the Content-Type header: \"{0}\".", exception.Message), "NGettext"); } } if (this.Headers.ContainsKey("Plural-Forms")) { //TODO: Plural forms parsing. } } this.Translations.Add(originalStrings[0], translatedStrings); } Trace.WriteLine("String parsing completed.", "NGettext"); } finally { #if DNXCORE50 reader.Dispose(); #else reader.Close(); #endif } }
/// <summary> /// Parses a GNU MO file from the given stream and loads all available data. /// </summary> /// <remarks> /// http://www.gnu.org/software/gettext/manual/html_node/MO-Files.html /// </remarks> /// <param name="stream">Stream that contain binary data in the MO file format</param> /// <returns>Parsed file data.</returns> public MoFile Parse(Stream stream) { #if !NETSTANDARD1_0 Trace.WriteLine("Trying to parse a MO file stream...", "NGettext"); #endif if (stream == null || stream.Length < 20) { throw new ArgumentException("Stream can not be null of less than 20 bytes long."); } var bigEndian = false; var reader = new BinaryReader(new ReadOnlyStreamWrapper(stream)); try { var magicNumber = reader.ReadUInt32(); if (magicNumber != MO_FILE_MAGIC) { // System.IO.BinaryReader does not respect machine endianness and always uses LittleEndian // So we need to detect and read BigEendian files by ourselves if (_ReverseBytes(magicNumber) == MO_FILE_MAGIC) { #if !NETSTANDARD1_0 Trace.WriteLine("BigEndian file detected. Switching readers...", "NGettext"); #endif bigEndian = true; ((IDisposable)reader).Dispose(); reader = new BigEndianBinaryReader(new ReadOnlyStreamWrapper(stream)); } else { throw new ArgumentException("Invalid stream: can not find MO file magic number."); } } var revision = reader.ReadInt32(); var parsedFile = new MoFile(new Version(revision >> 16, revision & 0xffff), this.DefaultEncoding, bigEndian); #if !NETSTANDARD1_0 Trace.WriteLine(String.Format("MO File Revision: {0}.{1}.", parsedFile.FormatRevision.Major, parsedFile.FormatRevision.Minor), "NGettext"); #endif if (parsedFile.FormatRevision.Major > MAX_SUPPORTED_VERSION) { throw new CatalogLoadingException(String.Format("Unsupported MO file major revision: {0}.", parsedFile.FormatRevision.Major)); } var stringCount = reader.ReadInt32(); var originalTableOffset = reader.ReadInt32(); var translationTableOffset = reader.ReadInt32(); // We don't support hash tables and system dependent segments. #if !NETSTANDARD1_0 Trace.WriteLine(String.Format("MO File contains {0} strings.", stringCount), "NGettext"); #endif var originalTable = new StringOffsetTable[stringCount]; var translationTable = new StringOffsetTable[stringCount]; #if !NETSTANDARD1_0 Trace.WriteLine(String.Format("Trying to parse strings using encoding \"{0}\"...", parsedFile.Encoding), "NGettext"); #endif reader.BaseStream.Seek(originalTableOffset, SeekOrigin.Begin); for (int i = 0; i < stringCount; i++) { originalTable[i].Length = reader.ReadInt32(); originalTable[i].Offset = reader.ReadInt32(); } reader.BaseStream.Seek(translationTableOffset, SeekOrigin.Begin); for (int i = 0; i < stringCount; i++) { translationTable[i].Length = reader.ReadInt32(); translationTable[i].Offset = reader.ReadInt32(); } for (int i = 0; i < stringCount; i++) { var originalStrings = this._ReadStrings(reader, originalTable[i].Offset, originalTable[i].Length, parsedFile.Encoding); var translatedStrings = this._ReadStrings(reader, translationTable[i].Offset, translationTable[i].Length, parsedFile.Encoding); if (originalStrings.Length == 0 || translatedStrings.Length == 0) continue; if (originalStrings[0].Length == 0) { // MO file meta data processing foreach (var headerText in translatedStrings[0].Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries)) { var separatorIndex = headerText.IndexOf(':'); if (separatorIndex > 0) { var headerName = headerText.Substring(0, separatorIndex); var headerValue = headerText.Substring(separatorIndex + 1).Trim(); parsedFile.Headers.Add(headerName, headerValue.Trim()); } } if (this.AutoDetectEncoding && parsedFile.Headers.ContainsKey("Content-Type")) { try { var contentType = new ContentType(parsedFile.Headers["Content-Type"]); if (!String.IsNullOrEmpty(contentType.CharSet)) { parsedFile.Encoding = Encoding.GetEncoding(contentType.CharSet); #if !NETSTANDARD1_0 Trace.WriteLine(String.Format("File encoding switched to \"{0}\" (\"{1}\" requested).", parsedFile.Encoding, contentType.CharSet), "NGettext"); #endif } } catch (Exception exception) { throw new CatalogLoadingException(String.Format("Unable to change parser encoding using the Content-Type header: \"{0}\".", exception.Message), exception); } } } parsedFile.Translations.Add(originalStrings[0], translatedStrings); } #if !NETSTANDARD1_0 Trace.WriteLine("String parsing completed.", "NGettext"); #endif return parsedFile; } finally { ((IDisposable)reader).Dispose(); } }