Skip to content

Commit

Permalink
Some refactorings and minor fixes.
Browse files Browse the repository at this point in the history
  • Loading branch information
VitaliiTsilnyk committed Jan 15, 2016
1 parent 88ccc86 commit 475d014
Show file tree
Hide file tree
Showing 12 changed files with 456 additions and 366 deletions.
8 changes: 4 additions & 4 deletions src/NGettext/Catalog.cs
Expand Up @@ -49,7 +49,7 @@ public IPluralRule PluralRule

/// <summary>
/// Initializes a new instance of the <see cref="Catalog"/> class that has no translations
/// using the current UI culture info and default plural rule.
/// using the current UI culture info and plural rule generated by DefaultPluralRuleGenerator for the current UI culture.
/// </summary>
public Catalog()
: this(CultureInfo.CurrentUICulture)
Expand All @@ -58,14 +58,14 @@ public Catalog()

/// <summary>
/// Initializes a new instance of the <see cref="Catalog"/> class that has no translations
/// using given culture info and default plural rule.
/// using given culture info and plural rule generated by DefaultPluralRuleGenerator for given culture.
/// </summary>
/// <param name="cultureInfo">Culture info.</param>
public Catalog(CultureInfo cultureInfo)
{
this.CultureInfo = cultureInfo;
this.Translations = new Dictionary<string, string[]>();
this.PluralRule = Plural.PluralRule.Default;
this.PluralRule = (new DefaultPluralRuleGenerator()).CreateRule(cultureInfo);
}

/// <summary>
Expand Down Expand Up @@ -93,7 +93,7 @@ public Catalog(ILoader loader, CultureInfo cultureInfo)
}
catch (FileNotFoundException exception)
{
// Supress FileNotFound exceptions
// Suppress FileNotFound exceptions
Trace.WriteLine(String.Format("Translation file loading fail: {0}", exception.Message), "NGettext");
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/NGettext/Loaders/MoAstPluralLoader.cs
Expand Up @@ -55,7 +55,7 @@ public MoAstPluralLoader(Stream moStream, MoFileParser parser)
/// <param name="domain"></param>
/// <param name="localeDir"></param>
public MoAstPluralLoader(string domain, string localeDir)
: base(domain, localeDir, new AstPluralRuleGenerator(), new MoFileParser())
: base(domain, localeDir, new AstPluralRuleGenerator())
{
}

Expand All @@ -66,7 +66,7 @@ public MoAstPluralLoader(string domain, string localeDir)
/// </summary>
/// <param name="filePath"></param>
public MoAstPluralLoader(string filePath)
: base(filePath, new AstPluralRuleGenerator(), new MoFileParser())
: base(filePath, new AstPluralRuleGenerator())
{
}

Expand All @@ -77,7 +77,7 @@ public MoAstPluralLoader(string filePath)
/// </summary>
/// <param name="moStream"></param>
public MoAstPluralLoader(Stream moStream)
: base(moStream, new AstPluralRuleGenerator(), new MoFileParser())
: base(moStream, new AstPluralRuleGenerator())
{
}
}
Expand Down
52 changes: 52 additions & 0 deletions src/NGettext/Loaders/MoFile.cs
@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace NGettext.Loaders
{
/// <summary>
/// Represents a parsed MO file data.
/// </summary>
public class MoFile
{
/// <summary>
/// Gets parsed file's format revision.
/// </summary>
public Version FormatRevision { get; protected set; }

/// <summary>
/// Gets a value that indicates whenever loaded file was in the BigEndian format.
/// </summary>
public bool BigEndian { get; protected set; }

/// <summary>
/// Gets or sets file's encoding.
/// </summary>
public Encoding Encoding { get; set; }

/// <summary>
/// Gets parsed file's meta data.
/// </summary>
public Dictionary<string, string> Headers { get; protected set; }

/// <summary>
/// Gets parsed file's translation strings.
/// </summary>
public Dictionary<string, string[]> Translations { get; protected set; }

/// <summary>
/// Initializes a new instance of the <see cref="MoFile"/> class.
/// </summary>
/// <param name="formatRevision">File format revision.</param>
/// <param name="encoding">File encoding.</param>
/// <param name="bigEndian">File endianness.</param>
public MoFile(Version formatRevision, Encoding encoding = null, bool bigEndian = false)
{
this.FormatRevision = formatRevision;
this.BigEndian = bigEndian;
this.Encoding = encoding;
this.Headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
this.Translations = new Dictionary<string, string[]>();
}
}
}
100 changes: 39 additions & 61 deletions src/NGettext/Loaders/MoFileParser.cs
@@ -1,9 +1,7 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;

namespace NGettext.Loaders
{
Expand All @@ -27,48 +25,35 @@ private struct StringOffsetTable
}

/// <summary>
/// Current encoding for decoding all strings in given MO file.
/// Default encoding for decoding all strings in given MO file.
/// Must be binary compatible with US-ASCII to be able to read file headers.
/// </summary>
/// <remarks>
/// Default value is UTF-8 as it is compatible with required by specifications us-ascii.
/// This value could be changed when DetectEncoding property is set to True and the MO file
/// contains a valid charset name in the Content-Type header.
/// Default value is UTF-8 as it is compatible with required by specifications US-ASCII.
/// </remarks>
public Encoding Encoding { get; set; }
public Encoding DefaultEncoding { get; set; }

/// <summary>
/// Gets or sets a value that indicates whenever the parser can detect file encoding using the Content-Type MIME header.
/// </summary>
public bool DetectEncoding { get; set; }
public bool AutoDetectEncoding { get; set; }

/// <summary>
/// Gets a value that indicates whenever loaded file was in the big-endian format.
/// Initializes a new instance of the <see cref="MoFileParser"/> class with UTF-8 as default encoding and with enabled automatic encoding detection.
/// </summary>
public bool IsBigEndian { get; protected set; }

/// <summary>
/// Gets parsed file's format revision.
/// </summary>
public Version FormatRevision { get; protected set; }

/// <summary>
/// Gets parsed file's metadata.
/// </summary>
public Dictionary<string, string> Headers { get; protected set; }

/// <summary>
/// Gets parsed file's translation strings.
/// </summary>
public Dictionary<string, string[]> Translations { get; protected set; }
public MoFileParser()
{
this.DefaultEncoding = Encoding.UTF8;
this.AutoDetectEncoding = true;
}

/// <summary>
/// Initializes a new instance of the <see cref="MoFileParser"/> class.
/// Initializes a new instance of the <see cref="MoFileParser"/> class using given default encoding and given automatic encoding detection option.
/// </summary>
public MoFileParser()
public MoFileParser(Encoding defaultEncoding, bool autoDetectEncoding = true)
{
this.Encoding = Encoding.UTF8;
this.DetectEncoding = true;
this._Init();
this.DefaultEncoding = defaultEncoding;
this.AutoDetectEncoding = autoDetectEncoding;
}

/// <summary>
Expand All @@ -78,28 +63,29 @@ public MoFileParser()
/// 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)
/// <returns>Parsed file data.</returns>
public MoFile 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 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 little-endian
// So we need to detect and read big-endian files by ourselves
// 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)
{
Trace.WriteLine("Big Endian file detected. Switching readers...", "NGettext");
this.IsBigEndian = true;
Trace.WriteLine("BigEndian file detected. Switching readers...", "NGettext");
bigEndian = true;
((IDisposable)reader).Dispose();
reader = new BigEndianBinaryReader(new ReadOnlyStreamWrapper(stream));
}
Expand All @@ -110,13 +96,13 @@ public void Parse(Stream stream)
}

var revision = reader.ReadInt32();
this.FormatRevision = new Version(revision >> 16, revision & 0xffff);
var parsedFile = new MoFile(new Version(revision >> 16, revision & 0xffff), this.DefaultEncoding, bigEndian);

Trace.WriteLine(String.Format("MO File Revision: {0}.{1}.", this.FormatRevision.Major, this.FormatRevision.Minor), "NGettext");
Trace.WriteLine(String.Format("MO File Revision: {0}.{1}.", parsedFile.FormatRevision.Major, parsedFile.FormatRevision.Minor), "NGettext");

if (this.FormatRevision.Major > MAX_SUPPORTED_VERSION)
if (parsedFile.FormatRevision.Major > MAX_SUPPORTED_VERSION)
{
throw new Exception(String.Format("Unsupported MO file major revision: {0}.", this.FormatRevision.Major));
throw new Exception(String.Format("Unsupported MO file major revision: {0}.", parsedFile.FormatRevision.Major));
}

var stringCount = reader.ReadInt32();
Expand All @@ -130,7 +116,7 @@ public void Parse(Stream stream)
var originalTable = new StringOffsetTable[stringCount];
var translationTable = new StringOffsetTable[stringCount];

Trace.WriteLine(String.Format("Trying to parse strings using encoding \"{0}\"...", this.Encoding), "NGettext");
Trace.WriteLine(String.Format("Trying to parse strings using encoding \"{0}\"...", parsedFile.Encoding), "NGettext");

reader.BaseStream.Seek(originalTableOffset, SeekOrigin.Begin);
for (int i = 0; i < stringCount; i++)
Expand All @@ -149,32 +135,32 @@ public void Parse(Stream stream)

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);
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 metadata processing
// MO file meta data 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());
parsedFile.Headers.Add(header[0], header[1].Trim());
}
}

if (this.DetectEncoding && this.Headers.ContainsKey("Content-Type"))
if (this.AutoDetectEncoding && parsedFile.Headers.ContainsKey("Content-Type"))
{
try
{
var contentType = new ContentType(this.Headers["Content-Type"]);
var contentType = new ContentType(parsedFile.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");
parsedFile.Encoding = Encoding.GetEncoding(contentType.CharSet);
Trace.WriteLine(String.Format("File encoding switched to \"{0}\" (\"{1}\" requested).", parsedFile.Encoding, contentType.CharSet), "NGettext");
}
}
catch (Exception exception)
Expand All @@ -184,31 +170,23 @@ public void Parse(Stream stream)
}
}

this.Translations.Add(originalStrings[0], translatedStrings);
parsedFile.Translations.Add(originalStrings[0], translatedStrings);
}

Trace.WriteLine("String parsing completed.", "NGettext");

return parsedFile;
}
finally
{
((IDisposable)reader).Dispose();
}
}

private void _Init()
{
this.Headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
this.Translations = new Dictionary<string, string[]>();
this.FormatRevision = null;
this.IsBigEndian = false;
}

private string[] _ReadStrings(BinaryReader reader, int offset, int length)
private string[] _ReadStrings(BinaryReader reader, int offset, int length, Encoding encoding)
{
reader.BaseStream.Seek(offset, SeekOrigin.Begin);
var stringBytes = reader.ReadBytes(length);
return this.Encoding.GetString(stringBytes, 0, stringBytes.Length).Split('\0');
return encoding.GetString(stringBytes, 0, stringBytes.Length).Split('\0');
}

private static uint _ReverseBytes(uint value)
Expand Down

0 comments on commit 475d014

Please sign in to comment.