1 #include "strings.h"
2 #include "greek.h"
3 #include "mathdefs.h"
4 #include <sstream>
5 #include <iomanip>
6 #include <cstring>
7 #include <cstdarg>
8 #include <cstdio>
9 #include <cstdlib>
10
11 using std::string;
12 using std::vector;
13 using std::ostringstream;
14
15 // Note: if the character to tokenize is repeated, null strings will
16 // not be created. E.g., "safs;;bq;g" with tokenizer ';' will result
17 // in three strings, not four. To avoid this problem, data files
18 // should always use a space to represent a null argument: "safs; ;bq;g"
StringList(const std::string & s,char tokenizer)19 StringList::StringList(const std::string &s, char tokenizer) :
20 vector<string>(), c_strings(0), c_strings_size(0)
21 {
22 size_t posn = 0, newposn = 0;
23
24 while (newposn < s.size()) {
25 posn = s.find_first_not_of(tokenizer, newposn);
26 newposn = s.find(tokenizer, posn);
27 if (posn < s.size())
28 push_back(s.substr(posn, newposn - posn));
29 }
30 }
31
32 string
flatten(char spacer) const33 StringList::flatten(char spacer) const
34 {
35 string result;
36 citerate_until (StringList, *this, i, -1)
37 result += (*i + spacer);
38 result += *(end() - 1);
39 return result;
40 }
41
42 char **
c_str() const43 StringList::c_str() const
44 {
45 delstrings();
46 c_strings_size = size();
47 c_strings = new char * [c_strings_size];
48 citerate (StringList, *this, ptr) {
49 size_t i = ptr - begin();
50 c_strings[i] = new char [(*ptr).size() + 1];
51 std::strcpy(c_strings[i], (*ptr).c_str());
52 }
53 return c_strings;
54 }
55
56 // This function is to fix the apparent lack of a zero-padding command
57 // in <iomanip> (setfill() doesn't seem to do anything)...
58 static string
pad(int width,double d,char c)59 pad(int width, double d, char c)
60 {
61 int chars = (d >= 10.0) ? width - FLOOR(std::log10(d)) - 1 : width - 1;
62 return (chars > 0) ? std::string(chars, c) : "";
63 }
64
65 // Right now this function just looks for the degree symbol \xB0, checks
66 // to make sure it's in UTF-8 form \xC2\xB0, and if not makes it so.
67 // XXX: This should obviously be much more general.
68 void
utf8ize(string & s)69 starstrings::utf8ize(string &s)
70 {
71 const static unsigned char deg_iso8859 = 0xB0;
72 const static unsigned char deg_utf8_prefix = 0xC2;
73
74 string::size_type posn = s.find(deg_iso8859);
75 for ( ; posn < s.size(); posn = s.find(deg_iso8859, posn + 1))
76 if (!posn || posn < s.size() &&
77 static_cast<unsigned char>(s[posn - 1]) < 0x80)
78 s.insert(posn++, 1, deg_utf8_prefix);
79 }
80
81 // Convert "alpha" to the UTF-8 character for alpha, etc.
82 void
addgreek(string & s)83 starstrings::addgreek(string &s)
84 {
85 int i = 1;
86 while (i < NUM_GREEK_LETTERS && !
87 compare_n(s, Greek[i].name, Greek[i].name.size()))
88 i++;
89 if (i < NUM_GREEK_LETTERS)
90 s = string(Greek[i].utf8) + s.substr(Greek[i].name.size());
91 }
92
93 string
ftoa(double d,int precision,int width,bool zeropadding)94 starstrings::ftoa(double d, int precision, int width, bool zeropadding)
95 {
96 // Which will take more characters: N.NNNNe+MM or NNNNN000 ?
97 // First form uses precision + 1[.] + 4[e+MM] = precision + 5 characters
98 // Second form uses n := MM + 1 characters
99 // Hence if precision < n <= precision + 5 we should force the
100 // non-exponential form by setting precision := n
101
102 double mag = std::fabs(d);
103 if (mag >= 1) {
104 // # of digits left of decimal point
105 int n_digits = 1 + FLOOR(std::log10(mag));
106 if (n_digits > precision && n_digits <= precision + 5)
107 precision = n_digits;
108 }
109
110 ostringstream o;
111 o.precision(precision);
112 if (width) {
113 if (zeropadding)
114 o << pad(width, std::fabs(d), '0');
115 else
116 o << pad(width, std::fabs(d), ' ');
117 }
118 o << starmath::sigdigits(d, precision);
119 return o.str();
120 }
121
122 string
ltoa(long int i,int width,bool zeropadding)123 starstrings::ltoa(long int i, int width, bool zeropadding)
124 {
125 ostringstream o;
126 if (width) {
127 if (zeropadding)
128 o << pad(width, std::labs(i), '0');
129 else
130 o << std::setw(width);
131 }
132 o << i;
133 return o.str();
134 }
135
136 // It would be nice to write this function such that it could take std::string
137 // as a printable argument. Unfortunately there seems no way to do this
138 // outside of GNU C's non-portable register_printf_function().
139 string
ssprintf(const char * format,...)140 starstrings::ssprintf(const char * format, ...)
141 {
142 // It's not clear whether or not vsprintf() and vsnprintf() are supposed
143 // to be in namespace std or in the global namespace. Hedge our bets:
144 using namespace std;
145
146 char test[2], * buffer;
147 va_list args;
148
149 // Figure out how much space is needed. Supposedly the printf() family
150 // return the number of bytes that would have been written even if not
151 // all of them are. Hope this is true...
152 va_start(args, format);
153 size_t size = vsnprintf(test, 2, format, args);
154 va_end(args);
155
156 // Write the result to a string
157 buffer = new char[size + 1];
158 va_start(args, format);
159 vsprintf(buffer, format, args);
160 string result(buffer);
161
162 // clean up
163 va_end(args);
164 delete [] buffer;
165 return result;
166 }
167
168 StringList
dec_to_strs(double dec,bool symbols)169 starstrings::dec_to_strs(double dec, bool symbols)
170 {
171 int deg, min;
172 double sec;
173 StringList result;
174 result.reserve(3);
175
176 dec /= RAD_PER_DEGREE;
177 result.push_back((dec < 0) ? "-" : (dec > 0) ? "+" : " ");
178 if (dec < 0) dec *= -1;
179
180 deg = FLOOR(dec);
181 min = FLOOR(FRAC(dec) * 60);
182 sec = starmath::roundoff((FRAC(dec) * 60 - min) * 60, 3);
183
184 // test to make sure we don't have min or sec == 60
185 string sec_str = starstrings::ftoa(sec, 4, 2, true);
186 if (sec_str == string("60")) { sec_str = "00"; min++; }
187 if (min == 60) { min = 0; deg++; }
188 if (deg >= 90) { deg = 90; min = 0; sec_str = "00"; }
189
190 result[0] += starstrings::itoa(deg, 2, true);
191 result.push_back(starstrings::itoa(min, 2, true));
192 result.push_back(sec_str);
193
194 if (symbols) {
195 result[0] += DEGREE_UTF8;
196 result[1] += "'";
197 result[2] += '"';
198 }
199 return result;
200 }
201
202
203 StringList
ra_to_strs(double ra,bool celestial_coords,bool symbols)204 starstrings::ra_to_strs(double ra, bool celestial_coords, bool symbols)
205 {
206 int h, m;
207 double s;
208 StringList result;
209 result.reserve(3);
210
211 while (ra < 0) ra += 2 * M_PI;
212 ra /= (celestial_coords ? RAD_PER_HOUR : RAD_PER_DEGREE);
213 h = FLOOR(ra);
214 m = FLOOR(FRAC(ra) * 60);
215 s = starmath::roundoff((FRAC(ra) * 60 - m) * 60, 3);
216
217 // test to make sure we don't have m or s == 60
218 string s_str = starstrings::ftoa(s, 4, 2, true);
219 if (s_str == string("60")) { s_str = "00"; m++; }
220 if (m == 60) { m = 0; h++; }
221 if (h >= 24 && celestial_coords) { h %= 24; }
222 if (h >= 360 && ! celestial_coords) { h %= 360; }
223
224 result.push_back(starstrings::itoa(h, celestial_coords ? 2 : 3, true));
225 result.push_back(starstrings::itoa(m, 2, true));
226 result.push_back(s_str);
227
228 if (symbols) {
229 result[0] += (celestial_coords ?
230 /* TRANSLATORS: This is the abbreviation for
231 "hours of right ascension". */
232 _("h") : DEGREE_UTF8);
233 result[1] += (celestial_coords ?
234 /* TRANSLATORS: This is the abbreviation for
235 "minutes of right ascension". */
236 _("m") : "'");
237 result[2] += (celestial_coords ?
238 /* TRANSLATORS: This is the abbreviation for
239 "seconds of right ascension". */
240 _("s") : "\"");
241 }
242 return result;
243 }
244
245
246 double
strs_to_dec(const StringList & decstrings)247 starstrings::strs_to_dec(const StringList &decstrings)
248 {
249 if (! decstrings.size()) return 0;
250
251 double result = std::fabs(starmath::atof(decstrings[0]));
252 if (decstrings.size() > 1) result += starmath::atof(decstrings[1]) / 60.0;
253 if (decstrings.size() > 2) result += starmath::atof(decstrings[2]) / 3600.0;
254
255 if (decstrings[0].find('-') < decstrings[0].size())
256 result *= -1;
257
258 return result * RAD_PER_DEGREE;
259 }
260
261
262 double
strs_to_ra(const StringList & rastrings,bool celestial_coords)263 starstrings::strs_to_ra(const StringList &rastrings,
264 bool celestial_coords)
265 {
266 if (! rastrings.size()) return 0;
267
268 double result = starmath::atof(rastrings[0]);
269 if (rastrings.size() > 1) result += starmath::atof(rastrings[1]) / 60.0;
270 if (rastrings.size() > 2) result += starmath::atof(rastrings[2]) / 3600.0;
271
272 return result * (celestial_coords ? RAD_PER_HOUR : RAD_PER_DEGREE);
273 }
274
275 double
strs_to_dec(const string & d,const string & m,const string & s)276 starstrings::strs_to_dec(const string &d, const string &m, const string &s)
277 {
278 StringList temp;
279 temp.push_back(d);
280 temp.push_back(m);
281 temp.push_back(s);
282 return strs_to_dec(temp);
283 }
284
285 double
strs_to_ra(const string & h,const string & m,const string & s,bool celestial_coords)286 starstrings::strs_to_ra(const string &h, const string &m, const string &s,
287 bool celestial_coords)
288 {
289 StringList temp;
290 temp.push_back(h);
291 temp.push_back(m);
292 temp.push_back(s);
293 return strs_to_ra(temp, celestial_coords);
294 }
295
296
297 // Distance units: in order, LY, pc, kpc, AU, km, mi.
298 double distance_conversion[N_DIST] = { 1.0, 1.0/LY_PER_PARSEC,
299 0.001/LY_PER_PARSEC,
300 AU_PER_LY, KM_PER_LY, MILE_PER_LY };
301
302 const char * distance_cname[N_DIST] = { "LY","pc","kpc","AU","km","miles" };
303 const char * distance_name[N_DIST] = {
304 /* Translators: LY is the abbreviation for "light-year" */
305 _("LY"), "pc", "kpc",
306 /* Translators: AU is the abbreviation for "astronomical unit" */
307 _("AU"), "km",
308 _("miles") };
309
310 distance_unit
str_to_unit(const string & s)311 starstrings::str_to_unit(const string & s)
312 {
313 for (unsigned i = 0; i < N_DIST; i++)
314 if (string(distance_cname[i]) == s)
315 return static_cast<distance_unit>(i);
316 // if we got here, string corresponds to invalid unit; assume the default
317 return static_cast<distance_unit>(0);
318 }
319
320 std::string
distance_to_str(double distance_ly,distance_unit unit)321 starstrings::distance_to_str(double distance_ly, distance_unit unit)
322 {
323 return starstrings::ftoa(distance_ly * distance_conversion[unit], 5)
324 + " " + distance_name[unit];
325 }
326
327 // "unit" pointer is to an array of 4 distance_unit enums, containing in order
328 // the desired large-scale, medium-scale, small-scale and very-small-scale
329 // units.
330 std::string
distance_to_str(double distance_ly,const distance_unit * unit)331 starstrings::distance_to_str(double distance_ly, const distance_unit * unit)
332 {
333 const static distance_unit default_units[4] =
334 { DIST_LY, DIST_LY, DIST_AU, DIST_KM };
335 if (! unit) unit = default_units;
336
337 double mag = std::fabs(distance_ly);
338 if (mag * distance_conversion[unit[1]] >= 1000.)
339 return distance_to_str(distance_ly, unit[0]);
340 else if (mag == 0 || mag * distance_conversion[unit[1]] >= 0.01)
341 return distance_to_str(distance_ly, unit[1]);
342 else if (mag * distance_conversion[unit[2]] >= 0.01)
343 return distance_to_str(distance_ly, unit[2]);
344 else
345 return distance_to_str(distance_ly, unit[3]);
346 }
347