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 ('\u 0004' );
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 ('\u 0004' );
114+
115+ parts = msgid.split ('\u 0000' );
116+ msgid = parts.first;
117+ parts.removeAt (0 );
118+
119+ translation['msgid' ] = msgid;
120+ msgidPlural = parts.join ('\u 0000' );
121+
122+ if (! msgidPlural.isEmpty) {
123+ translation['msgid_plural' ] = msgidPlural;
124+ }
125+
126+ msgstr = msgstr.split ('\u 0000' );
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+ }
0 commit comments