The Battle for Wesnoth  1.19.0-dev
catalog_metadata.hpp
Go to the documentation of this file.
1 // (C) Copyright 2015 - 2017 Christopher Beck
2 
3 // Distributed under the Boost Software License, Version 1.0. (See accompanying
4 // file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
5 
6 #ifndef SPIRIT_PO_CATALOG_METADATA_HPP_INCLUDED
7 #define SPIRIT_PO_CATALOG_METADATA_HPP_INCLUDED
8 
9 #ifndef BOOST_SPIRIT_USE_PHOENIX_V3
10 #define BOOST_SPIRIT_USE_PHOENIX_V3
11 #endif
12 
13 #include <spirit_po/exceptions.hpp>
14 
15 #include <boost/spirit/include/qi.hpp>
16 #include <boost/fusion/include/std_pair.hpp>
17 #include <string>
18 
19 namespace spirit_po {
20 
21 namespace qi = boost::spirit::qi;
22 
23 typedef unsigned int uint;
24 typedef std::pair<uint, std::string> num_plurals_info;
25 
27  std::string project_id;
28  std::string language;
29  std::string language_team;
30  std::string last_translator;
31 
34 
35  std::string charset;
36 
38  : project_id()
39  , language()
40  , language_team()
41  , last_translator()
42  , num_plural_forms(0)
44  {}
45 
46 private:
47  std::string find_header_line(const std::string & header, const std::string & label) {
48  size_t idx = header.find(label);
49  if (idx == std::string::npos) {
50  return "";
51  }
52  auto it = header.begin() + idx + label.size();
53  while (it != header.end() && *it == ' ') { ++it; }
54 
55  auto e = it;
56  while (e != header.end() && *e != '\n') { ++e; }
57  return std::string(it, e);
58  }
59 
60  template <typename Iterator>
61  struct num_plurals_grammar : qi::grammar<Iterator, num_plurals_info()> {
62  qi::rule<Iterator, num_plurals_info()> main;
64  using qi::lit;
65  main = qi::skip(' ') [ lit("nplurals=") >> qi::uint_ >> lit(';') >> lit("plural=") ] >> (*qi::char_);
66  }
67  };
68 
69 #define SPIRIT_PO_DEFAULT_CHARSET "UTF-8"
70 
71  template <typename Iterator>
72  struct content_type_grammar : qi::grammar<Iterator, std::string()> {
73  qi::rule<Iterator, std::string()> main;
75  using qi::lit;
76  using qi::omit;
77  using qi::skip;
78  main = skip(' ')[ omit[ *(qi::char_ - ';') >> lit(';') ] >> ((lit("charset=") >> *(qi::char_)) | qi::attr(SPIRIT_PO_DEFAULT_CHARSET)) ];
79  }
80  };
81 
82 public:
83  // nonempty return is an error message
84  std::string parse_header(const std::string & header) {
85  const char * const default_charset = SPIRIT_PO_DEFAULT_CHARSET;
86 #undef SPIRIT_PO_DEFAULT_CHARSET
87 
88  project_id = find_header_line(header, "Project-Id-Version:");
89  language = find_header_line(header, "Language:");
90  language_team = find_header_line(header, "Language-Team:");
91  last_translator = find_header_line(header, "Last-Translator:");
92 
93  std::string content_type_line = find_header_line(header, "Content-Type:");
94  if (content_type_line.size()) {
95  auto it = content_type_line.begin();
96  auto end = content_type_line.end();
97  content_type_grammar<decltype(it)> gram;
98  std::string ct;
99  if (qi::parse(it, end, gram, ct)) {
100  charset = ct;
101  if (charset != "ASCII" && charset != "UTF-8") {
102  return "PO file declared charset of '" + charset + "', but spirit_po only supports UTF-8 and ASCII for this.";
103  }
104  }
105  } else {
106  // Assume defaults for charset
107  charset = default_charset;
108  }
109 
110  std::string content_transfer_encoding = find_header_line(header, "Content-Transfer-Encoding:");
111  if (content_transfer_encoding.size()) {
112  auto it = content_transfer_encoding.begin();
113  auto end = content_transfer_encoding.end();
114  if (!qi::phrase_parse(it, end, qi::lit("8bit"), qi::ascii::space)) {
115  return "PO header 'Content-Transfer-Encoding' must be '8bit' if specified, but PO file declared '" + content_transfer_encoding + "'";
116  }
117  }
118 
119  std::string num_plurals_line = find_header_line(header, "Plural-Forms:");
120 
121  if (num_plurals_line.size()) {
122  auto it = num_plurals_line.begin();
123  auto end = num_plurals_line.end();
124 
125  num_plurals_grammar<decltype(it)> gram;
127  if (qi::parse(it, end, gram, info)) {
128  num_plural_forms = info.first;
130  } else {
131  num_plural_forms = 0;
133  return "Failed to parse Plural-Forms entry -- stopped at:\n" + string_iterator_context(num_plurals_line, it);
134  }
135  } else {
136  num_plural_forms = 2;
137  plural_forms_function_string = "n != 1";
138  }
139  return "";
140  }
141 
142  // check if this metadata is compatible with another metadata (number of plural forms, maybe other criteria)
143  // return a nonempty string containing error message if they are not compatible.
144  std::string check_compatibility(const catalog_metadata & other) const {
145  if (num_plural_forms != other.num_plural_forms) {
146  return std::string{"Num plural forms mismatch. this = "} + std::to_string(num_plural_forms) + " other = " + std::to_string(other.num_plural_forms);
147  }
148  return "";
149  }
150 };
151 
152 } // end namespace spirit_po
153 
154 #endif // SPIRIT_PO_CATALOG_METADATA_HPP_INCLUDED
#define SPIRIT_PO_DEFAULT_CHARSET
std::string label
What to show in the filter's drop-down list.
Definition: manager.cpp:209
logger & info()
Definition: log.cpp:314
std::string string_iterator_context(const std::string &str, std::string::const_iterator it)
Definition: exceptions.hpp:36
std::pair< uint, std::string > num_plurals_info
unsigned int uint
Definition: catalog.hpp:39
qi::rule< Iterator, num_plurals_info()> main
std::string parse_header(const std::string &header)
std::string check_compatibility(const catalog_metadata &other) const
std::string find_header_line(const std::string &header, const std::string &label)
#define e