MofileReader
moFileReader.hpp
Go to the documentation of this file.
1 /*
2  * moFileReader - A simple .mo-File-Reader
3  * Copyright (C) 2009 Domenico Gentner (scorcher24@gmail.com)
4  * Copyright (C) 2018-2021 Edgar (Edgar@AnotherFoxGuy.com)
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  * notice, this list of conditions and the following disclaimer.
13  *
14  * 2. Redistributions in binary form must reproduce the above copyright
15  * notice, this list of conditions and the following disclaimer in the
16  * documentation and/or other materials provided with the distribution.
17  *
18  * 3. The names of its contributors may not be used to endorse or promote
19  * products derived from this software without specific prior written
20  * permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
25  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
26  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
27  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
28  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
29  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
30  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
31  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
32  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33  */
34 #ifndef __MOFILEREADER_SINGLE_INCLUDE_H_INCLUDED__
35 #define __MOFILEREADER_SINGLE_INCLUDE_H_INCLUDED__
36 
37 #if defined(_MSC_VER)
38  #pragma warning(disable : 4267)
39 #endif /* _MSC_VER */
40 
41 #include <cstring> // this is for memset when compiling with gcc.
42 #include <deque>
43 #include <fstream>
44 #include <map>
45 #include <sstream>
46 #include <string>
47 
48 //-------------------------------------------------------------
49 // Path-Seperators are different on other OS.
50 //-------------------------------------------------------------
51 #ifndef MO_PATHSEP
52  #ifdef WIN32
53  #define MO_PATHSEP std::string("\\")
54  #else
55  #define MO_PATHSEP std::string("/")
56  #endif
57 #endif
58 
59 //-------------------------------------------------------------
60 // Defines the beginning of the namespace moFileLib.
61 //-------------------------------------------------------------
62 #ifndef MO_BEGIN_NAMESPACE
63  #define MO_BEGIN_NAMESPACE \
64  namespace moFileLib \
65  {
66 #endif
67 //-------------------------------------------------------------
68 // Ends the current namespace.
69 //-------------------------------------------------------------
70 #ifndef MO_END_NAMESPACE
71  #define MO_END_NAMESPACE }
72 #endif
73 
163 
164 const std::string g_css = R"(
165 body {
166  background-color: black;
167  color: silver;
168 }
169 table {
170  width: 80%;
171 }
172 th {
173  background-color: orange;
174  color: black;
175 }
176 hr {
177  color: red;
178  width: 80%;
179  size: 5px;
180 }
181 a:link{
182  color: gold;
183 }
184 a:visited{
185  color: grey;
186 }
187 a:hover{
188  color:blue;
189 }
190 .copyleft{
191  font-size: 12px;
192  text-align: center;
193 })";
194 
203 {
206  {
207  }
208 
211 
214 
217 
220 };
221 
233 {
235  typedef std::deque<moTranslationPairInformation> moTranslationPairList;
236 
240  m_offsetHashtable(0), m_reversed(false)
241  {
242  }
243 
246 
249 
252 
255 
258 
261 
264 
269 
272 };
273 
295 {
296  protected:
298  typedef std::map<std::string, std::string> moLookupList;
299 
301  typedef std::map<std::string, moLookupList> moContextLookupList;
302 
303  public:
305  static const unsigned int MagicNumber = 0x950412DE;
306 
308  static const unsigned int MagicReversed = 0xDE120495;
309 
311  static const char ContextSeparator = '\x04';
312 
315  {
318 
321 
324 
327 
330 
333 
339  };
340 
349  moFileReader::eErrorCode ParseData(const std::string &data)
350  {
351  // Opening the file.
352  std::stringstream stream(data);
353 
354  return ReadStream(stream);
355  }
356 
365  eErrorCode ReadFile(const char *filename)
366  {
367  // Opening the file.
368  std::ifstream stream(filename, std::ios_base::binary | std::ios_base::in);
369  if (!stream.is_open())
370  {
371  m_error = std::string("Cannot open File ") + std::string(filename);
373  }
374 
375  eErrorCode res = ReadStream(stream);
376  stream.close();
377 
378  return res;
379  }
380 
386  template <typename T> eErrorCode ReadStream(T &stream)
387  {
388  // Creating a file-description.
389  moFileInfo moInfo;
390 
391  // Reference to the List inside moInfo.
393 
394  // Read in all the 4 bytes of fire-magic, offsets and stuff...
395  stream.read((char *)&moInfo.m_magicNumber, 4);
396  stream.read((char *)&moInfo.m_fileVersion, 4);
397  stream.read((char *)&moInfo.m_numStrings, 4);
398  stream.read((char *)&moInfo.m_offsetOriginal, 4);
399  stream.read((char *)&moInfo.m_offsetTranslation, 4);
400  stream.read((char *)&moInfo.m_sizeHashtable, 4);
401  stream.read((char *)&moInfo.m_offsetHashtable, 4);
402 
403  if (stream.bad())
404  {
405  m_error = "Stream bad during reading. The .mo-file seems to be invalid or has bad descriptions!";
407  }
408 
409  // Checking the Magic Number
410  if (MagicNumber != moInfo.m_magicNumber)
411  {
412  if (MagicReversed != moInfo.m_magicNumber)
413  {
414  m_error = "The Magic Number does not match in all cases!";
416  }
417  else
418  {
419  moInfo.m_reversed = true;
420  m_error = "Magic Number is reversed. We do not support this yet!";
422  }
423  }
424 
425  // Now we search all Length & Offsets of the original strings
426  for (int i = 0; i < moInfo.m_numStrings; i++)
427  {
429  stream.read((char *)&_str.m_orLength, 4);
430  stream.read((char *)&_str.m_orOffset, 4);
431  if (stream.bad())
432  {
433  m_error = "Stream bad during reading. The .mo-file seems to be invalid or has bad descriptions!";
435  }
436 
437  TransPairInfo.push_back(_str);
438  }
439 
440  // Get all Lengths & Offsets of the translated strings
441  // Be aware: The Descriptors already exist in our list, so we just mod. refs from the deque.
442  for (int i = 0; i < moInfo.m_numStrings; i++)
443  {
444  moTranslationPairInformation &_str = TransPairInfo[i];
445  stream.read((char *)&_str.m_trLength, 4);
446  stream.read((char *)&_str.m_trOffset, 4);
447  if (stream.bad())
448  {
449  m_error = "Stream bad during reading. The .mo-file seems to be invalid or has bad descriptions!";
451  }
452  }
453 
454  // Normally you would read the hash-table here, but we don't use it. :)
455 
456  // Now to the interesting part, we read the strings-pairs now
457  for (int i = 0; i < moInfo.m_numStrings; i++)
458  {
459  // We need a length of +1 to catch the trailing \0.
460  int orLength = TransPairInfo[i].m_orLength + 1;
461  int trLength = TransPairInfo[i].m_trLength + 1;
462 
463  int orOffset = TransPairInfo[i].m_orOffset;
464  int trOffset = TransPairInfo[i].m_trOffset;
465 
466  // Original
467  char *original = new char[orLength];
468  memset(original, 0, sizeof(char) * orLength);
469 
470  stream.seekg(orOffset);
471  stream.read(original, orLength);
472 
473  if (stream.bad())
474  {
475  m_error = "Stream bad during reading. The .mo-file seems to be invalid or has bad descriptions!";
477  }
478 
479  // Translation
480  char *translation = new char[trLength];
481  memset(translation, 0, sizeof(char) * trLength);
482 
483  stream.seekg(trOffset);
484  stream.read(translation, trLength);
485 
486  if (stream.bad())
487  {
488  m_error = "Stream bad during reading. The .mo-file seems to be invalid or has bad descriptions!";
490  }
491 
492  std::string original_str = original;
493  std::string translation_str = translation;
494  auto ctxSeparator = original_str.find(ContextSeparator);
495 
496  // Store it in the map.
497  if (ctxSeparator == std::string::npos)
498  {
499  m_lookup[original_str] = translation_str;
500  numStrings++;
501  }
502  else
503  {
504  // try-catch for handling out_of_range exceptions
505  try
506  {
507  m_lookup_context[original_str.substr(0, ctxSeparator)]
508  [original_str.substr(ctxSeparator + 1, original_str.length())] = translation_str;
509  numStrings++;
510  }
511  catch (...)
512  {
513  m_error = "Stream bad during reading. The .mo-file seems to be invalid or has bad descriptions!";
514  return moFileReader::EC_ERROR;
515  }
516  }
517 
518  // Cleanup...
519  delete[] original;
520  delete[] translation;
521  }
522 
523  // Done :)
525  }
526 
531  std::string Lookup(const char *id) const
532  {
533  if (m_lookup.empty()) return id;
534  auto iterator = m_lookup.find(id);
535 
536  return iterator == m_lookup.end() ? id : iterator->second;
537  }
538 
545  std::string LookupWithContext(const char *context, const char *id) const
546  {
547  if (m_lookup_context.empty()) return id;
548  auto iterator = m_lookup_context.find(context);
549 
550  if (iterator == m_lookup_context.end()) return id;
551  auto iterator2 = iterator->second.find(id);
552 
553  return iterator2 == iterator->second.end() ? id : iterator2->second;
554  }
555 
557  const std::string &GetErrorDescription() const
558  {
559  return m_error;
560  }
561 
563  void ClearTable()
564  {
565  m_lookup.clear();
566  m_lookup_context.clear();
567  numStrings = 0;
568  }
569 
574  unsigned int GetNumStrings() const
575  {
576  return numStrings;
577  }
578 
586  static eErrorCode ExportAsHTML(const std::string &infile, const std::string &filename = "", const std::string &css = g_css)
587  {
588  // Read the file
589  moFileReader reader;
590  moFileReader::eErrorCode r = reader.ReadFile(infile.c_str());
591  if (r != moFileReader::EC_SUCCESS) { return r; }
592  if (reader.m_lookup.empty()) { return moFileReader::EC_TABLEEMPTY; }
593 
594  // Beautify Output
595  std::string fname;
596  unsigned int pos = infile.find_last_of(MO_PATHSEP);
597  if (pos != std::string::npos) { fname = infile.substr(pos + 1, infile.length()); }
598  else
599  {
600  fname = infile;
601  }
602 
603  // if there is no filename given, we set it to the .mo + html, e.g. test.mo.html
604  std::string htmlfile(filename);
605  if (htmlfile.empty()) { htmlfile = infile + std::string(".html"); }
606 
607  // Ok, now prepare output.
608  std::ofstream stream(htmlfile.c_str());
609  if (stream.is_open())
610  {
611  stream << R"(<!DOCTYPE HTML PUBLIC "- //W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">)"
612  << std::endl;
613  stream << "<html><head><style type=\"text/css\">\n" << std::endl;
614  stream << css << std::endl;
615  stream << "</style>" << std::endl;
616  stream << R"(<meta http-equiv="content-type" content="text/html; charset=utf-8">)" << std::endl;
617  stream << "<title>Dump of " << fname << "</title></head>" << std::endl;
618  stream << "<body>" << std::endl;
619  stream << "<center>" << std::endl;
620  stream << "<h1>" << fname << "</h1>" << std::endl;
621  stream << R"(<table border="1"><th colspan="2">Project Info</th>)" << std::endl;
622 
623  std::stringstream parsee;
624  parsee << reader.Lookup("");
625 
626  while (!parsee.eof())
627  {
628  char buffer[1024];
629  parsee.getline(buffer, 1024);
630  std::string name;
631  std::string value;
632 
633  reader.GetPoEditorString(buffer, name, value);
634  if (!(name.empty() || value.empty()))
635  {
636  stream << "<tr><td>" << name << "</td><td>" << value << "</td></tr>" << std::endl;
637  }
638  }
639  stream << "</table>" << std::endl;
640  stream << "<hr noshade/>" << std::endl;
641 
642  // Now output the content
643  stream << R"(<table border="1"><th colspan="2">Content</th>)" << std::endl;
644  for (const auto &it : reader.m_lookup)
645  {
646  if (!it.first.empty()) // Skip the empty msgid, its the table we handled above.
647  {
648  stream << "<tr><td>" << it.first << "</td><td>" << it.second << "</td></tr>" << std::endl;
649  }
650  }
651  stream << "</table><br/>" << std::endl;
652 
653  // Separate tables for each context
654  for (const auto &it : reader.m_lookup_context)
655  {
656  stream << R"(<table border="1"><th colspan="2">)" << it.first << "</th>" << std::endl;
657  for (const auto &its : it.second)
658  {
659  stream << "<tr><td>" << its.first << "</td><td>" << its.second << "</td></tr>" << std::endl;
660  }
661  stream << "</table><br/>" << std::endl;
662  }
663 
664  stream << "</center>" << std::endl;
665  stream << "<div class=\"copyleft\">File generated by <a href=\"https://github.com/AnotherFoxGuy/MofileReader\" "
666  "target=\"_blank\">moFileReaderSDK</a></div>"
667  << std::endl;
668  stream << "</body></html>" << std::endl;
669  stream.close();
670  }
671  else
672  {
674  }
675 
677  }
678 
679  protected:
681  std::string m_error;
682 
687  unsigned long SwapBytes(unsigned long in)
688  {
689  unsigned long b0 = (in >> 0) & 0xff;
690  unsigned long b1 = (in >> 8) & 0xff;
691  unsigned long b2 = (in >> 16) & 0xff;
692  unsigned long b3 = (in >> 24) & 0xff;
693 
694  return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
695  }
696 
697  private:
698  // Holds the lookup-table
699  moLookupList m_lookup;
700  moContextLookupList m_lookup_context;
701 
702  int numStrings = 0;
703 
704  // Replaces < with ( to satisfy html-rules.
705  static void MakeHtmlConform(std::string &_inout)
706  {
707  std::string temp = _inout;
708  for (unsigned int i = 0; i < temp.length(); i++)
709  {
710  if (temp[i] == '>') { _inout.replace(i, 1, ")"); }
711  if (temp[i] == '<') { _inout.replace(i, 1, "("); }
712  }
713  }
714 
715  // Extracts a value-pair from the po-edit-information
716  bool GetPoEditorString(const char *_buffer, std::string &_name, std::string &_value)
717  {
718  std::string line(_buffer);
719  size_t first = line.find_first_of(':');
720 
721  if (first != std::string::npos)
722  {
723  _name = line.substr(0, first);
724  _value = line.substr(first + 1, line.length());
725 
726  // Replace <> with () for Html-Conformity.
727  MakeHtmlConform(_value);
728  MakeHtmlConform(_name);
729 
730  // Remove spaces from front and end.
731  Trim(_value);
732  Trim(_name);
733 
734  return true;
735  }
736  return false;
737  }
738 
739  // Removes spaces from front and end.
740  static void Trim(std::string &_in)
741  {
742  while (_in[0] == ' ')
743  {
744  _in = _in.substr(1, _in.length());
745  }
746  while (_in[_in.length()] == ' ')
747  {
748  _in = _in.substr(0, _in.length() - 1);
749  }
750  }
751 };
752 
753 #ifndef MO_NO_CONVENIENCE_CLASS
768 {
769  private:
770  // Private Contructor and Copy-Constructor to avoid
771  // that this class is instanced.
773  {
774  }
775 
777 
778  moFileReaderSingleton &operator=(const moFileReaderSingleton &)
779  {
780  return *this;
781  }
782 
783  public:
788  {
789  static moFileReaderSingleton theoneandonly;
790  return theoneandonly;
791  }
792 };
793 
798 inline moFileReader::eErrorCode moReadMoFile(const char *_filename)
799 {
801  return r;
802 }
803 
808 inline std::string _(const char *id)
809 {
810  std::string r = moFileReaderSingleton::GetInstance().Lookup(id);
811  return r;
812 }
813 
815 inline void moFileClearTable()
816 {
818 }
819 
821 inline std::string moFileGetErrorDescription()
822 {
824  return r;
825 }
826 
829 {
831  return r;
832 }
833 #endif
834 
835 #if defined(_MSC_VER)
836  #pragma warning(default : 4251)
837 #endif /* _MSC_VER */
838 
840 
841 #endif /* __MOFILEREADER_SINGLE_INCLUDE_H_INCLUDED__ */
This class is a gettext-replacement.
unsigned long SwapBytes(unsigned long in)
Swap the endianness of a 4 byte WORD.
static const unsigned int MagicNumber
The Magic Number describes the endianess of bytes on the system.
const std::string & GetErrorDescription() const
Returns the Error Description.
std::string Lookup(const char *id) const
Returns the searched translation or returns the input.
eErrorCode ReadFile(const char *filename)
Reads a .mo-file.
static eErrorCode ExportAsHTML(const std::string &infile, const std::string &filename="", const std::string &css=g_css)
Exports the whole content of the .mo-File as .html.
static const unsigned int MagicReversed
If the Magic Number is Reversed, we need to swap the bytes.
moFileReader::eErrorCode ParseData(const std::string &data)
Reads a .mo-file.
eErrorCode
The possible errorcodes for methods of this class.
@ EC_ERROR
Indicates an error.
@ EC_FILENOTFOUND
The given File was not found.
@ EC_MAGICNUMBER_NOMATCH
The magic number did not match.
@ EC_TABLEEMPTY
Empty Lookup-Table (returned by ExportAsHTML())
@ EC_MAGICNUMBER_REVERSED
The magic number is reversed.
@ EC_FILEINVALID
The file is invalid.
@ EC_SUCCESS
Indicated success.
std::map< std::string, std::string > moLookupList
Type for the map which holds the translation-pairs later.
std::string LookupWithContext(const char *context, const char *id) const
Returns the searched translation or returns the input, restricted to the context given by context....
std::map< std::string, moLookupList > moContextLookupList
Type for the 2D map which holds the translation-pairs later.
unsigned int GetNumStrings() const
Returns the Number of Entries in our Lookup-Table.
eErrorCode ReadStream(T &stream)
Reads data from a stream.
std::string m_error
Keeps the last error as String.
static const char ContextSeparator
The character that is used to separate context strings.
void ClearTable()
Empties the Lookup-Table.
Convenience Class.
static moFileReaderSingleton & GetInstance()
Singleton-Accessor.
#define MO_BEGIN_NAMESPACE
void moFileClearTable()
Resets the Lookup-Table.
#define MO_END_NAMESPACE
moFileReader::eErrorCode moReadMoFile(const char *_filename)
Reads the .mo-File.
std::string moFileGetErrorDescription()
Returns the last known error as string or an empty class.
MO_BEGIN_NAMESPACE const std::string g_css
#define MO_PATHSEP
std::string _(const char *id)
Looks for the spec. string to translate.
int moFileGetNumStrings()
Returns the number of entries loaded from the .mo-File.
Describes the "Header" of a .mo-File.
int m_offsetTranslation
Offset of the Table of the Translated Strings.
std::deque< moTranslationPairInformation > moTranslationPairList
Type for the list of all Translation-Pair-Descriptions.
moFileInfo()
Constructor.
int m_numStrings
Number of Strings in the .mo-file.
int m_offsetHashtable
The Offset of the Hashtable.
int m_sizeHashtable
Size of 1 Entry in the Hashtable.
int m_fileVersion
The File Version, 0 atm according to the manpage.
int m_offsetOriginal
Offset of the Table of the Original Strings.
bool m_reversed
Tells you if the bytes are reversed.
int m_magicNumber
The Magic Number, compare it to g_MagicNumber.
moTranslationPairList m_translationPairInformation
A list containing offset and length of the strings in the file.
Keeps the Description of translated and original strings.
int m_orOffset
Offset of the Original String (absolute)
int m_orLength
Length of the Original String.
int m_trLength
Length of the Translated String.
int m_trOffset
Offset of the Translated String (absolute)
moTranslationPairInformation()
Constructor.