1 #ifdef HAVE_CONFIG_H
2 # include <config.h>
3 #endif
5 #include <cmath>
6 #include <cerrno>
7 #include <glib.h>
9 #include "io/simple-sax.h"
10 #include "util/units.h"
11 #include "path-prefix.h"
12 #include "streq.h"
14 namespace Inkscape {
15 namespace Util {
17 class UnitsSAXHandler : public Inkscape::IO::FlatSaxHandler
18 {
19 public:
20 UnitsSAXHandler(UnitTable *table) : FlatSaxHandler(), tbl(table) {}
21 virtual ~UnitsSAXHandler() {}
23 virtual void _startElement(xmlChar const *name, xmlChar const **attrs);
24 virtual void _endElement(xmlChar const *name);
26 UnitTable *tbl;
27 bool primary;
28 bool skip;
29 Unit unit;
30 };
33 #define BUFSIZE (255)
35 /**
36 * Returns the suggested precision to use for displaying numbers
37 * of this unit.
38 */
39 int Unit::defaultDigits() const {
40 int factor_digits = int(log10(factor));
41 if (factor_digits < 0) {
42 g_warning("factor = %f, factor_digits = %d", factor, factor_digits);
43 g_warning("factor_digits < 0 - returning 0");
44 return 0;
45 } else {
46 return factor_digits;
47 }
48 }
50 /**
51 * Initializes the unit tables and identifies the primary unit types.
52 *
53 * The primary unit's conversion factor is required to be 1.00
54 */
55 UnitTable::UnitTable()
56 {
57 // if we swich to the xml file, don't forget to force locale to 'C'
58 // load("share/ui/units.xml"); // <-- Buggy
59 gchar *filename = g_build_filename(INKSCAPE_UIDIR, "units.txt", NULL);
60 loadText(filename);
61 g_free(filename);
62 }
64 UnitTable::~UnitTable() {
65 UnitMap::iterator iter = _unit_map.begin();
66 while (iter != _unit_map.end()) {
67 delete (*iter).second;
68 ++iter;
69 }
70 }
72 /** Add a new unit to the table */
73 void
74 UnitTable::addUnit(Unit const &u, bool primary) {
75 _unit_map[u.abbr] = new Unit(u);
76 if (primary) {
77 _primary_unit[u.type] = u.abbr;
78 }
79 }
81 /** Retrieve a given unit based on its string identifier */
82 Unit
83 UnitTable::getUnit(Glib::ustring const &unit_abbr) const {
84 UnitMap::const_iterator iter = _unit_map.find(unit_abbr);
85 if (iter != _unit_map.end()) {
86 return *((*iter).second);
87 } else {
88 return Unit();
89 }
90 }
92 /** Remove a unit definition from the given unit type table */
93 bool
94 UnitTable::deleteUnit(Unit const &u) {
95 if (u.abbr == _primary_unit[u.type]) {
96 // Cannot delete the primary unit type since it's
97 // used for conversions
98 return false;
99 }
100 UnitMap::iterator iter = _unit_map.find(u.abbr);
101 if (iter != _unit_map.end()) {
102 delete (*iter).second;
103 _unit_map.erase(iter);
104 return true;
105 } else {
106 return false;
107 }
108 }
110 /** Returns true if the given string 'name' is a valid unit in the table */
111 bool
112 UnitTable::hasUnit(Glib::ustring const &unit) const {
113 UnitMap::const_iterator iter = _unit_map.find(unit);
114 return (iter != _unit_map.end());
115 }
117 /** Provides an iteratable list of items in the given unit table */
118 UnitTable::UnitMap
119 UnitTable::units(UnitType type) const
120 {
121 UnitMap submap;
122 for (UnitMap::const_iterator iter = _unit_map.begin();
123 iter != _unit_map.end(); ++iter) {
124 if (((*iter).second)->type == type) {
125 submap.insert(UnitMap::value_type((*iter).first, new Unit(*((*iter).second))));
126 }
127 }
129 return submap;
130 }
132 /** Returns the default unit abbr for the given type */
133 Glib::ustring
134 UnitTable::primary(UnitType type) const {
135 return _primary_unit[type];
136 }
138 /** Merges the contents of the given file into the UnitTable,
139 possibly overwriting existing unit definitions. This loads
140 from a text file */
141 bool
142 UnitTable::loadText(Glib::ustring const &filename) {
143 char buf[BUFSIZE];
145 // Open file for reading
146 FILE * f = fopen(filename.c_str(), "r");
147 if (f == NULL) {
148 g_warning("Could not open units file '%s': %s\n",
149 filename.c_str(), strerror(errno));
150 g_warning("* INKSCAPE_DATADIR is: '%s'\n", INKSCAPE_DATADIR);
151 g_warning("* INKSCAPE_UIDIR is: '%s'\n", INKSCAPE_UIDIR);
152 return false;
153 }
155 // bypass current locale in order to make
156 // sscanf read floats with '.' as a separator
157 // set locate to 'C' and keep old locale
158 char *old_locale;
159 old_locale = g_strdup (setlocale (LC_NUMERIC, NULL));
160 setlocale (LC_NUMERIC, "C");
162 while (fgets(buf, BUFSIZE, f) != NULL) {
163 char name[BUFSIZE];
164 char plural[BUFSIZE];
165 char abbr[BUFSIZE];
166 char type[BUFSIZE];
167 double factor;
168 char primary[BUFSIZE];
170 int nchars = 0;
171 // locate is set to C, scanning %lf should work _everywhere_
172 if (sscanf(buf, "%s %s %s %s %lf %s %n",
173 name, plural, abbr, type, &factor,
174 primary, &nchars) != 6) {
175 // Skip the line - doesn't appear to be valid
176 continue;
177 }
178 g_assert(nchars < BUFSIZE);
180 char *desc = buf;
181 desc += nchars; // buf is now only the description
183 // insert into _unit_map
184 Unit u;
185 u.name = name;
186 u.name_plural = plural;
187 u.abbr = abbr;
188 u.description = desc;
189 u.factor = factor;
191 if (streq(type, "DIMENSIONLESS")) {
192 u.type = UNIT_TYPE_DIMENSIONLESS;
193 } else if (streq(type, "LINEAR")) {
194 u.type = UNIT_TYPE_LINEAR;
195 } else if (streq(type, "RADIAL")) {
196 u.type = UNIT_TYPE_RADIAL;
197 } else if (streq(type, "FONT_HEIGHT")) {
198 u.type = UNIT_TYPE_FONT_HEIGHT;
199 } else {
200 g_warning("Skipping unknown unit type '%s' for %s.\n",
201 type, name);
202 continue;
203 }
205 // if primary is 'Y', list this unit as a primary
206 addUnit(u, (primary[0]=='Y' || primary[0]=='y'));
208 }
210 // set back the saved locale
211 setlocale (LC_NUMERIC, old_locale);
212 g_free (old_locale);
214 // close file
215 if (fclose(f) != 0) {
216 g_warning("Error closing units file '%s': %s\n",
217 filename.c_str(), strerror(errno));
218 return false;
219 }
221 return true;
222 }
224 bool
225 UnitTable::load(Glib::ustring const &filename) {
226 UnitsSAXHandler handler(this);
228 int result = handler.parseFile( filename.c_str() );
229 if ( result != 0 ) {
230 // perhaps
231 g_warning("Problem loading units file '%s': %d\n",
232 filename.c_str(), result);
233 return false;
234 }
236 return true;
237 }
239 /** Saves the current UnitTable to the given file. */
240 bool
241 UnitTable::save(Glib::ustring const &filename) {
243 // open file for writing
244 FILE *f = fopen(filename.c_str(), "w");
245 if (f == NULL) {
246 g_warning("Could not open units file '%s': %s\n",
247 filename.c_str(), strerror(errno));
248 return false;
249 }
251 // write out header
252 // foreach item in _unit_map, sorted alphabetically by type and then unit name
253 // sprintf a line
254 // name
255 // name_plural
256 // abbr
257 // type
258 // factor
259 // PRI - if listed in primary unit table, 'Y', else 'N'
260 // description
261 // write line to the file
263 // close file
264 if (fclose(f) != 0) {
265 g_warning("Error closing units file '%s': %s\n",
266 filename.c_str(), strerror(errno));
267 return false;
268 }
270 return true;
271 }
274 void UnitsSAXHandler::_startElement(xmlChar const *name, xmlChar const **attrs)
275 {
276 if (streq("unit", (char const *)name)) {
277 // reset for next use
278 unit.name.clear();
279 unit.name_plural.clear();
280 unit.abbr.clear();
281 unit.description.clear();
282 unit.type = UNIT_TYPE_DIMENSIONLESS;
283 unit.factor = 1.0;
284 primary = false;
285 skip = false;
287 for ( int i = 0; attrs[i]; i += 2 ) {
288 char const *const key = (char const *)attrs[i];
289 if (streq("type", key)) {
290 char const *type = (char const*)attrs[i+1];
291 if (streq(type, "DIMENSIONLESS")) {
292 unit.type = UNIT_TYPE_DIMENSIONLESS;
293 } else if (streq(type, "LINEAR")) {
294 unit.type = UNIT_TYPE_LINEAR;
295 } else if (streq(type, "RADIAL")) {
296 unit.type = UNIT_TYPE_RADIAL;
297 } else if (streq(type, "FONT_HEIGHT")) {
298 unit.type = UNIT_TYPE_FONT_HEIGHT;
299 } else {
300 g_warning("Skipping unknown unit type '%s' for %s.\n", type, name);
301 skip = true;
302 }
303 } else if (streq("pri", key)) {
304 primary = attrs[i+1][0] == 'y' || attrs[i+1][0] == 'Y';
305 }
306 }
307 }
308 }
310 void UnitsSAXHandler::_endElement(xmlChar const *xname)
311 {
312 char const *const name = (char const *) xname;
313 if (streq("name", name)) {
314 unit.name = data;
315 } else if (streq("plural", name)) {
316 unit.name_plural = data;
317 } else if (streq("abbr", name)) {
318 unit.abbr = data;
319 } else if (streq("factor", name)) {
320 // TODO make sure we use the right conversion
321 unit.factor = atol(data.c_str());
322 } else if (streq("description", name)) {
323 unit.description = data;
324 } else if (streq("unit", name)) {
325 if (!skip) {
326 tbl->addUnit(unit, primary);
327 }
328 }
329 }
331 } // namespace Util
332 } // namespace Inkscape
335 /*
336 Local Variables:
337 mode:c++
338 c-file-style:"stroustrup"
339 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
340 indent-tabs-mode:nil
341 fill-column:99
342 End:
343 */
344 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :