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