Skip to content

Commit 542aa57

Browse files
committed
Mo parser
0 parents  commit 542aa57

30 files changed

+1503
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.dart_tool
2+
.idea
3+
.packages

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## 0.0.1
2+
3+
- Initial version

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# MO parser for UTF-8 encoded *.mo files
2+
3+
## Example
4+
5+
```dart
6+
Parser parser = new Parser(buffer);
7+
Map translateTable = parser.parse();
8+
```

analysis_options.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
analyzer:
2+
# exclude:
3+
# - path/to/excluded/files/**
4+
5+
# Lint rules and documentation, see http://dart-lang.github.io/linter/lints
6+
linter:
7+
rules:
8+
- cancel_subscriptions
9+
- hash_and_equals
10+
- iterable_contains_unrelated_type
11+
- list_remove_unrelated_type
12+
- test_types_in_equals
13+
- unrelated_type_equality_checks
14+
- valid_regexps

example/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Parse
2+
3+
## MO files
4+
5+
```dart
6+
Parser parser = new Parser(buffer);
7+
Map translateTable = parser.parse();
8+
```

lib/mo_parser.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
library mo_parser;
2+
3+
import './src/parser.dart';
4+
export './src/parser.dart';
5+
6+
/// Parse *.mo file to Map object
7+
Map parser (List<int> buffer) {
8+
Parser parser = new Parser(buffer);
9+
return parser.parse();
10+
}

lib/src/parser.dart

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import 'dart:convert';
2+
import 'dart:typed_data';
3+
4+
import './utils/parse_header.dart';
5+
6+
/// Parser class
7+
/// Contains decoding and parse logic for passed List<int>
8+
class Parser {
9+
// Magic constant to check the endianness of the input file
10+
static const _MAGIC = 0x950412de;
11+
12+
// Default endian for read/write
13+
Endian _endian = Endian.little;
14+
ByteData _fileContents;
15+
Map _table;
16+
// Offset position for original strings table
17+
int _offsetOriginals;
18+
// Offset position for translation strings table
19+
int _offsetTranslations;
20+
// GetText revision nr, usually 0
21+
int _revision;
22+
// Total count of translated strings
23+
int _total;
24+
25+
Parser(List<int> fileContent) {
26+
this._fileContents = ByteData.view(Uint8List.fromList(fileContent).buffer);
27+
28+
this._table = {
29+
'charset': 'utf-8',
30+
'headers': null,
31+
'translations': {},
32+
};
33+
}
34+
35+
// Checks if number values in the input file are in big- or littleendian format.
36+
bool _checkMagick () {
37+
if (this._fileContents.getUint32(0, Endian.little) == Parser._MAGIC) {
38+
this._endian = Endian.little;
39+
return true;
40+
} else if (this._fileContents.getUint32(0, Endian.big) == Parser._MAGIC) {
41+
this._endian = Endian.big;
42+
return true;
43+
} else {
44+
return false;
45+
}
46+
}
47+
48+
// Read the original strings and translations from the input MO file. Use the
49+
// first translation string in the file as the header.
50+
void _loadTranslationTable () {
51+
int offsetOriginals = this._offsetOriginals;
52+
int offsetTranslations = this._offsetTranslations;
53+
int position, length;
54+
String msgid, msgstr;
55+
Iterable msgidRange, msgstrRange;
56+
57+
for (int i = 0; i < this._total; i++) {
58+
// msgid string
59+
length = this._fileContents.getUint32(offsetOriginals, this._endian);
60+
offsetOriginals += 4;
61+
position = this._fileContents.getUint32(offsetOriginals, this._endian);
62+
offsetOriginals += 4;
63+
msgidRange = this._fileContents.buffer.asUint8List().getRange(position, position + length);
64+
65+
// matching msgstr
66+
length = this._fileContents.getUint32(offsetTranslations, this._endian);
67+
offsetTranslations += 4;
68+
position = this._fileContents.getUint32(offsetTranslations, this._endian);
69+
offsetTranslations += 4;
70+
msgstrRange = this._fileContents.buffer.asUint8List().getRange(position, position + length);
71+
72+
if (i == 0 && msgidRange.toList().isEmpty) {
73+
this._handleCharset(msgstrRange);
74+
}
75+
76+
/**
77+
* dart:convert support limited quantity of charsets
78+
* https://api.dartlang.org/dev/2.1.1-dev.0.1/dart-convert/dart-convert-library.html
79+
*
80+
* More about issue
81+
* https://stackoverflow.com/questions/21142985/convert-a-string-from-iso-8859-2-to-utf-8-in-the-dart-language
82+
* https://stackoverflow.com/questions/51148729/how-to-manually-convert-between-latin-5-and-unicode-code-points
83+
*/
84+
msgid = utf8.decode(msgidRange.toList());
85+
msgstr = utf8.decode(msgstrRange.toList());
86+
87+
this._addString(msgid, msgstr);
88+
}
89+
90+
// dump the file contents object
91+
this._fileContents = null;
92+
}
93+
94+
void _handleCharset (Iterable headers) {
95+
String headersParsed = utf8.decode(headers.toList());
96+
97+
this._table['headers'] = parseHeader(headersStr: headersParsed);
98+
}
99+
100+
void _addString (dynamic msgid, dynamic msgstr) {
101+
final Map translation = {};
102+
List<String> parts;
103+
String msgctxt, msgidPlural;
104+
105+
msgid = msgid.split('\u0004');
106+
if (msgid.length > 1) {
107+
msgctxt = msgid.first;
108+
msgid.removeAt(0);
109+
translation['msgctxt'] = msgctxt;
110+
} else {
111+
msgctxt = '';
112+
}
113+
msgid = msgid.join('\u0004');
114+
115+
parts = msgid.split('\u0000');
116+
msgid = parts.first;
117+
parts.removeAt(0);
118+
119+
translation['msgid'] = msgid;
120+
msgidPlural = parts.join('\u0000');
121+
122+
if (!msgidPlural.isEmpty) {
123+
translation['msgid_plural'] = msgidPlural;
124+
}
125+
126+
msgstr = msgstr.split('\u0000');
127+
translation['msgstr'] = msgstr;
128+
129+
if (!this._table['translations'].containsKey(msgctxt)) {
130+
this._table['translations'][msgctxt] = {};
131+
}
132+
133+
this._table['translations'][msgctxt][msgid] = translation;
134+
}
135+
136+
/// Parses the MO object and returns translation table
137+
Map parse () {
138+
if (!this._checkMagick()) {
139+
return null;
140+
}
141+
142+
this._revision = this._fileContents.getUint32(4, this._endian);
143+
this._total = this._fileContents.getUint32(8, this._endian);
144+
this._offsetOriginals = this._fileContents.getUint32(12, this._endian);
145+
this._offsetTranslations = this._fileContents.getUint32(16, this._endian);
146+
147+
this._loadTranslationTable();
148+
149+
return this._table;
150+
}
151+
}

lib/src/utils/parse_header.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/// Parses a header string into an object of key-value pairs
2+
Map parseHeader({String headersStr = ''}) {
3+
final List<String> lines = headersStr.split('\n');
4+
final Map headers = {};
5+
6+
lines.forEach((line) {
7+
final List<String> parts = line.trim().split(':');
8+
final String key = parts.first.trim().toLowerCase();
9+
parts.removeAt(0);
10+
final String value = parts.join(':').trim();
11+
12+
if (key.isEmpty) {
13+
return;
14+
}
15+
16+
headers[key] = value;
17+
});
18+
19+
return headers;
20+
}

0 commit comments

Comments
 (0)